クリーンアーキテクチャ

一言で

ビジネスロジックを外部依存から隔離し、依存関係を一方向に制限する設計手法。

4層アーキテクチャ

Framework/Driver層 — Web、DB、外部API
Interface Adapter層 — コントローラー、ゲートウェイ
Use Case層 — アプリケーションロジック
Domain層 — ビジネスルール・エンティティ

ルール: 内側の層は外側の層を知らない。外→内への依存のみ。

ディレクトリ構成

src/
  domain/    ← エンティティ・ポート(外部依存ゼロ)
  usecases/  ← ユースケース(ドメインのみに依存)
  adapters/  ← DB・APIの実装(ポートを実装)
  frameworks/ ← Webルーティング・DI設定

いつ使うか

対象
中〜大規模で長期間保守するアプリ
ビジネスロジックが複雑(ドメインルールが多い)
外部依存(DB、API)が変わる可能性がある
小規模スクリプト・CLI(レイヤー分け過多)
プロトタイプ(動くもの最優先)

コード例 — Python

# === Domain層(外部依存ゼロ)===
# domain/entities.py
from dataclasses import dataclass

@dataclass
class Reservation:
    id: str; guest_name: str; party_size: int; date: str
    def is_valid(self) -> bool:
        return self.party_size > 0 and len(self.guest_name) > 0

# domain/ports.py(インターフェース定義)
from typing import Protocol

class ReservationRepository(Protocol):
    def save(self, reservation: Reservation) -> str: ...
    def find_by_date(self, date: str) -> list[Reservation]: ...

class NotificationSender(Protocol):
    def send_confirmation(self, email: str, reservation_id: str) -> None: ...

# === Use Case層 ===
# usecases/create_reservation.py
class CreateReservationUseCase:
    def __init__(self, repo: ReservationRepository,
                 notifier: NotificationSender):
        self.repo = repo; self.notifier = notifier

    def execute(self, guest_name: str, party_size: int, date: str) -> str:
        reservation = Reservation(id="", guest_name=guest_name,
                                  party_size=party_size, date=date)
        if not reservation.is_valid():
            raise ValueError("Invalid reservation")
        existing = self.repo.find_by_date(date)
        if len(existing) >= 10:
            raise ValueError("No availability")
        reservation_id = self.repo.save(reservation)
        self.notifier.send_confirmation(guest_name, reservation_id)
        return reservation_id

# === Adapter層(外部依存はここだけ)===
# adapters/sqlite_repository.py
import sqlite3
from domain.entities import Reservation
from domain.ports import ReservationRepository

class SqliteReservationRepository:
    def __init__(self, db_path: str):
        self.conn = sqlite3.connect(db_path)
    def save(self, reservation: Reservation) -> str:
        # SQLで保存
        ...
    def find_by_date(self, date: str) -> list[Reservation]:
        # SQLで検索
        ...

コード例 — TypeScript

// === Domain層 ===
// domain/entities.ts
export interface Reservation {
  id: string; guestName: string; partySize: number; date: string;
}
export function isValidReservation(r: Reservation): boolean {
  return r.partySize > 0 && r.guestName.length > 0;
}

// domain/ports.ts
export interface ReservationRepository {
  save(reservation: Reservation): Promise;
  findByDate(date: string): Promise;
}
export interface NotificationSender {
  sendConfirmation(email: string, reservationId: string): Promise;
}

// === Use Case層 ===
// usecases/createReservation.ts
export class CreateReservationUseCase {
  constructor(private repo: ReservationRepository,
              private notifier: NotificationSender) {}

  async execute(guestName: string, partySize: number,
                date: string): Promise {
    const reservation: Reservation = { id: "", guestName, partySize, date };
    if (!isValidReservation(reservation))
      throw new Error("Invalid reservation");
    const existing = await this.repo.findByDate(date);
    if (existing.length >= 10) throw new Error("No availability");
    const id = await this.repo.save(reservation);
    await this.notifier.sendConfirmation(guestName, id);
    return id;
  }
}

組み合わせ

DDD — ドメインモデルをDomain層に配置 TDD — Domain・UseCase層は外部依存なしで高速テスト 設計原則 — DIPがクリーンアーキテクチャの前提 BDD — UseCaseのテストにBDDが最適
CLAUDE.md用プロンプト:
## 開発手法: クリーンアーキテクチャ
- コードを4層に分ける: Domain → UseCase → Adapter → Framework
- 依存関係は内側にのみ向ける。Domain層は外部ライブラリに依存しない
- 外部依存(DB、API)はPort(Protocol/Interface)で抽象化、Adapter層で実装
- ディレクトリ構成: domain/ usecases/ adapters/ frameworks/
- テストはDomain・UseCase層を中心に(外部依存なしで高速)
← 開発手法セレクタに戻る ← ガイド一覧に戻る