DDD — ドメイン駆動設計

一言で

ソフトウェアの構造を「ビジネスの概念」に合わせて設計し、業務ルールを正確にコードで表現する。

5つの重要な概念

ユビキタス言語
開発者とビジネス側で同じ言葉を使う。コード内の名前も業務用語に。
エンティティ
同一性で識別されるオブジェクト。例: 予約IDで識別される「予約」
値オブジェクト
属性だけで識別されるオブジェクト。例: 「金額」は数値+通貨の組み合わせ
集約(Aggregate)
整合性を保つ単位。集約ルート経由でのみアクセス
ドメインイベント
業務上の出来事を明示的に表現。例: 「予約が確定した」

いつ使うか

対象
ビジネスルールが複雑(予約、在庫、契約、ワークフロー等)
ルールが頻繁に変わる(業務知識の変化にコードを追従させたい)
ユーザーとの会話から要件を整理したい
単純なCRUD(作る・読む・更新・消すだけのアプリ)
データの変換・加工が中心のツール

コード例 — Python

from dataclasses import dataclass
from enum import Enum

# --- 値オブジェクト(属性で識別・不変)---
@dataclass(frozen=True)
class GuestCount:
    value: int
    def __post_init__(self):
        if self.value < 1 or self.value > 20:
            raise ValueError(f"人数は1〜20人: {self.value}")

class ReservationStatus(Enum):
    PENDING = "pending"
    CONFIRMED = "confirmed"
    CANCELLED = "cancelled"

# --- エンティティ(IDで識別)---
@dataclass
class Reservation:
    id: str
    guest_name: str
    party_size: GuestCount
    date: str
    status: ReservationStatus = ReservationStatus.PENDING

    def confirm(self) -> None:
        """予約を確定(ドメインルール: PENDINGのみ確定可能)"""
        if self.status != ReservationStatus.PENDING:
            raise ValueError(f"確定できるのはPENDINGのみ: {self.status}")
        self.status = ReservationStatus.CONFIRMED

    def cancel(self) -> None:
        """予約をキャンセル(ドメインルール: CONFIRMEDのみ可能)"""
        if self.status != ReservationStatus.CONFIRMED:
            raise ValueError(f"キャンセルできるのはCONFIRMEDのみ: {self.status}")
        self.status = ReservationStatus.CANCELLED

# --- ドメインサービス(単一エンティティに属さないルール)---
class ReservationDomainService:
    @staticmethod
    def check_double_booking(new_res: Reservation, existing: list[Reservation]) -> bool:
        same = [r for r in existing if r.date == new_res.date
                and r.guest_name == new_res.guest_name
                and r.status != ReservationStatus.CANCELLED]
        return len(same) > 0

コード例 — TypeScript

// --- 値オブジェクト ---
export class GuestCount {
  readonly value: number;
  constructor(value: number) {
    if (value < 1 || value > 20) throw new Error(`人数は1〜20人: ${value}`);
    this.value = value;
  }
}

export enum ReservationStatus {
  Pending = "pending",
  Confirmed = "confirmed",
  Cancelled = "cancelled",
}

// --- エンティティ ---
export class Reservation {
  readonly id: string;
  readonly guestName: string;
  readonly partySize: GuestCount;
  readonly date: string;
  private _status: ReservationStatus;

  constructor(props: {
    id: string; guestName: string;
    partySize: GuestCount; date: string;
    status?: ReservationStatus;
  }) {
    this.id = props.id; this.guestName = props.guestName;
    this.partySize = props.partySize; this.date = props.date;
    this._status = props.status ?? ReservationStatus.Pending;
  }

  get status(): ReservationStatus { return this._status; }

  confirm(): void {
    if (this._status !== ReservationStatus.Pending)
      throw new Error("確定できるのはPendingのみ");
    this._status = ReservationStatus.Confirmed;
  }

  cancel(): void {
    if (this._status !== ReservationStatus.Confirmed)
      throw new Error("キャンセルできるのはConfirmedのみ");
    this._status = ReservationStatus.Cancelled;
  }
}

組み合わせ

クリーンアーキテクチャ — Domain層に配置 TDD — ドメインルールは外部依存なしでテスト可能 BDD — ユースケース→BDDシナリオ 設計原則 — SRP・ISPでモデル設計
CLAUDE.md用プロンプト:
## 開発手法: DDD(ドメイン駆動設計)
- ビジネスルールをドメイン層(domain/)に集約する
- 値オブジェクト: 不変(frozen=True / readonly)、業務上の制約をコンストラクタで検証
- エンティティ: IDで識別、状態遷移のルールをメソッドとして定義
- ドメインサービス: 単一エンティティに属さないルール(重複チェック等)
- コード内の名前は業務用語(ユビキタス言語)に合わせる
← 開発手法セレクタに戻る ← ガイド一覧に戻る