DDD 06 実務の注意(やりすぎ・TypeScript・AI開発)

更新日:

← DDD_05_アーキテクチャ(レイヤード・オニオン・クリーン・CQRS) / 入口 → DDD

1. よくある失敗

失敗症状対策
形だけ DDD(ライト DDD の誤用)Entity・Repository・UseCase のフォルダはあるが、中身は getter/setter とそのまま SQL部品より業務理解と言葉から。ルールがないなら層を作らない
何にでも適用設定画面の CRUD に5層・10ファイル。変更が遅くなるコアドメインだけ。支援・汎用はシンプルに
巨大な集約会員集約に注文・住所・ポイント履歴が全部ぶら下がる。読み込みが重い・更新が衝突不変条件を確認して小さく分割、他は ID 参照
ドメインモデル貧血症ルールが UseCase・画面・SQL に散らばるルールをエンティティ・値オブジェクトに寄せる
用語集がない / 更新されないコードと会話の言葉がずれる用語集をリポジトリに置き、レビューで確認
最初からマイクロサービス境界を間違えたまま分割し、サービス間通信だらけまずモジュラーモノリス。境界が安定してから分ける
DB テーブル = エンティティ と思い込むテーブル設計がそのままクラスにドメインモデルを先に考え、保存形式はリポジトリで変換

2. 段階的な取り入れ方

複雑さに応じて、上から順に必要になったら足す。

段階やること対象
0ユビキタス言語・用語集すべてのプロジェクト
1値オブジェクト / ブランド型で入力を検証済みの型にする金額・メール・日付範囲など
2エンティティに状態遷移のルールを持たせるステータスが3つ以上ある、遷移に条件がある
3集約とリポジトリ、ドメイン層を分離業務ルールが多いコアな機能
4ドメインイベント・CQRS複数集約・複数コンテキストの連携
5コンテキスト分割・サービス分割・イベントソーシング複数チーム・大規模

3. TypeScript での実践ポイント

  • クラスにこだわらなくてよい。関数型スタイル(型 + 純粋関数)でも DDD はできる
    // 状態ごとに型を分けると、不正な状態遷移がコンパイルエラーになる
    type Tentative = { kind: 'tentative'; id: string; heldAt: Date };
    type Confirmed = { kind: 'confirmed'; id: string; confirmedAt: Date };
    type Reservation = Tentative | Confirmed;
     
    function confirm(r: Tentative, now: Date): Confirmed {   // Confirmed は受け取れない
      return { kind: 'confirmed', id: r.id, confirmedAt: now };
    }
    → 判別共用体(TypeScript の型システム)で「ありえない状態を表現できなくする」
  • 境界で検証、内側は信頼:HTTP 入力は Zod などで検証して値オブジェクトに変換。ドメイン内部で再チェックを繰り返さない
  • エラーの扱い:業務ルール違反(「期限切れ」)は throw の代わりに Result 型で返すと、呼び出し側が処理を忘れにくい(neverthrow など)。チームでどちらかに統一する
  • ORM の型をドメインに漏らさない:Drizzle / Prisma の行の型を Order として使い回さない。マッパーで変換
  • テスト:ドメイン層は DB なしの単体テストで厚く。テスト名も業務の言葉で(「仮予約は24時間を過ぎると確定できない」)

4. 生成AI と DDD

AI は「それっぽい層構造」を大量に作るのが得意 = やりすぎ DDD を量産しやすい。逆に、言葉と境界をきちんと渡せば非常に良い相棒になる。

やること理由
用語集(ユビキタス言語)を CLAUDE.md や docs/ に置き、参照させるAI が勝手に User / Customer / Member を混ぜるのを防ぐ
どこまで DDD を適用するか明記する(「ordering/ だけ集約+リポジトリ。他は CRUD でよい」)指示がないと全機能に層を作りがち
フォルダ構成と依存ルールを伝える(「domain/ は外部ライブラリを import しない」)ドメイン層に ORM やフレームワークを混ぜられるのを防ぐ
業務ルールを箇条書きで渡し、テストから書かせる「仮予約は24時間で失効」などを先にテストにすると、ルールの漏れが見える
ドメインエキスパート役として質問させる(「このルールの例外を質問して」)境界条件の洗い出しに強い
生成されたコードで「ルールがどこにあるか」を確認UseCase や Route Handler にルールが書かれていたら差し戻す
イベントストーミングの叩き台を作らせる出来事・コマンド・集約候補を一覧化させ、人が業務の人と確認

AI に渡す指示の例

ordering コンテキストに「注文確定」ユースケースを追加して。
- 用語は docs/glossary.md に従う
- ルール: 明細0件は確定不可 / 確定後は明細変更不可 / 合計は明細の合計
- ルールは domain/order.ts の Order 集約に書き、まず domain のテストを書く
- application/ は手順だけ。認可(注文者本人か)チェックを入れる
- domain/ から drizzle-orm・next を import しない

AI が書いた DDD コードのチェックリスト

  • 業務の言葉(用語集)どおりの名前か。Manager / Helper / Data が増えていないか
  • 業務ルールがドメイン層にあるか(UseCase・ハンドラ・SQL に散っていないか)
  • エンティティの状態を外から直接書き換えられないか(公開 setter がないか)
  • 値オブジェクトが生成時に検証しているか、不変か
  • 集約が大きすぎないか、他の集約を直接持たず ID で参照しているか
  • リポジトリが集約単位か(明細用リポジトリなどがないか)
  • domain/ が ORM・フレームワーク・外部 API の型に依存していないか
  • UseCase に認可チェックがあるか
  • 単純な CRUD 部分にまで不要な層を作っていないか

5. まとめ

  • DDD は複雑な業務ルールに立ち向かうための道具。簡単な所には使わない
  • どんな規模でも価値があるのはユビキタス言語と値オブジェクト
  • 迷ったら「業務の人がこのコードを読んで、ルールが分かるか?」で判断する