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 はできる
→ 判別共用体(TypeScript の型システム)で「ありえない状態を表現できなくする」// 状態ごとに型を分けると、不正な状態遷移がコンパイルエラーになる 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 }; } - 境界で検証、内側は信頼: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 は複雑な業務ルールに立ち向かうための道具。簡単な所には使わない
- どんな規模でも価値があるのはユビキタス言語と値オブジェクト
- 迷ったら「業務の人がこのコードを読んで、ルールが分かるか?」で判断する