# AI用DDDガイドライン

更新日: 2026-08-29
言語: 日本語

## 目的

この文書は、AIがDDDの設計・実装を支援するときに使う判断基準です。パターンを機械的に当てはめるためではなく、業務の言葉とルールをコードへ正しく表すために使います。

## AIへの指示

- 最初に、解決したい業務上の問題、関係者が使う言葉、守るルールを確認する。
- 不明な業務ルールを推測で確定しない。仮定する場合は、仮定であることを明示する。
- EntityやValue Objectなどのパターン名から設計を始めない。業務上必要な同一性、値、ルールから必要な要素を選ぶ。
- コードを提案するときは、どの業務ルールをどこで守るのか説明する。
- 既存のドメインモデルと言葉を確認し、同じ意味に別の名前を増やさない。
- 判断に必要な情報が不足している場合は、実装を断定せず確認事項を示す。

## このプロジェクトで使う命名

- Entityのクラス名とファイル名には `Entity` を付ける。例: `RecruitmentEntity` / `RecruitmentEntity.ts`
- Value Objectのクラス名とファイル名には `ValueObject` を付ける。例: `ParticipantIdValueObject`
- Domain Serviceのクラス名とファイル名には `DomainService` を付ける。例: `ParticipationScheduleDomainService`
- Application Serviceのクラス名とファイル名には `ApplicationService` を付ける。例: `RequestParticipationApplicationService`
- interfaceの名前には `I` を付ける。例: `IRecruitmentRepository`
- Aggregate RootはEntityの役割であるため、クラス名に `AggregateRoot` は付けない。
- Repository interfaceはDomain側へ置き、DBやORMを使うRepositoryはInfrastructure側へ置く。
- Application Serviceにinterfaceを別定義するかは必須ではない。呼び出し口を実装から分ける必要がある場合に使う。

## 設計を提案するときの順番

1. 対象とする業務上の場面を具体例で示す。
2. 具体例から分かった言葉、関係、ルール、未確認事項を分ける。
3. 同一性が必要なもの、値として扱うもの、一緒にルールを守る範囲を整理する。
4. 業務上の判断をドメインモデルのどこで行うか決める。
5. Application Service、Repository、Transactionの役割を整理する。
6. コードとテストでルールを確かめ、分かったことをドメインモデルへ戻す。

## ドメインモデリング

### Entityはいつ作る？

- ルールID: `entity`
- 判断: 一つひとつを識別し、情報が変わっても区別して管理する必要があるものをEntityにする。
- 意味: Entityは、業務から見つけた概念を、同一性によって区別できる形でコードに表すためのドメインモデルの構成要素です。
- 理由: 値が変わっても同じ対象として扱う必要がある場合、識別子とライフサイクルをドメインモデルに明示する必要があります。

判断するときに確認すること

- 履歴や状態遷移を管理する
- 同じ属性でも別物として区別する
- 業務上の識別子が会話に現れる

良い例: 募集は開催場所を変更しても、募集IDを変えずに同じ募集として管理する。

避ける例: 表示用の都道府県一覧の各項目に、将来使うかもしれないという理由だけでEntity IDを付ける。

例外・補足: 永続化都合の主キーがあっても、それだけではドメイン上のEntityとは限りません。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/modeling/entity

### Value Objectにすべき基準は？

- ルールID: `value-object`
- 判断: 値の組み合わせで意味と等価性が決まり、独立したライフサイクルを持たない概念をValue Objectにする。
- 意味: Value Objectは、業務から見つけた値をコードで表すドメインモデルの構成要素です。識別子ではなく、持っている値によって同じかどうかを判断します。
- 理由: 検証と振る舞いを値の型へ閉じ込めると、不正な値や単位の取り違えを境界で防げます。

判断するときに確認すること

- 構成する値が同じなら交換可能
- 生成後は不変にできる
- 金額・期間・住所など固有のルールがある

良い例: 申込期限が有効な日時だけを受け取り、期限を過ぎているかを自身で判断する。

避ける例: 価格をnumber、通貨をstringとして別々に引き回し、各呼び出し元で検証する。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/modeling/value-object

### Aggregateはどの範囲で作る？

- ルールID: `aggregate`
- 判断: 一つの業務上の操作で、一緒にルールを守る必要がある範囲を一つのAggregateにする。
- 意味: Aggregateは、業務上の操作で一緒に確認するドメインモデルを、一つの単位として扱う範囲です。この範囲の中で業務ルールを守ります。
- 理由: 必要なドメインモデルを別々に変更できると、Aggregate全体で守る業務ルールが破られる可能性があるためです。

判断するときに確認すること

- 一つの業務上の操作で一緒に確認する
- 操作後もルールを満たす必要がある
- 同じトランザクションで変更する必要がある

良い例: 募集が申込期限、定員、参加者IDを確認し、条件を満たしたユーザーだけを参加者として追加する。

避ける例: 画面に一緒に表示するという理由だけで、関係するすべてのデータを一つのAggregateに含める。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/modeling/aggregate

### Aggregate RootはどのEntityにする？

- ルールID: `aggregate-root`
- 判断: Aggregateのルールを確認してから処理できるEntityを、Aggregate Rootにする。
- 意味: Aggregate Rootは、Aggregateを代表するEntityです。Aggregateの状態を変えるときは、Aggregate Rootのメソッドを呼びます。Aggregate Rootは、必要な業務ルールを確認してから処理します。
- 理由: 入口を一つにすると、Aggregate内部のEntityを直接変更して業務ルールを通り抜けることを防げます。

判断するときに確認すること

- 外部からIDで参照される
- 内部Entityの生成・変更を調整する
- トランザクション整合性の境界になる

良い例: RecruitmentEntity.requestParticipation()が期限、二重申請、定員を確認してから参加者IDを追加する。

避ける例: 呼び出し側が参加者IDの一覧へ直接追加し、募集のルールを確認しない。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/modeling/aggregate-root

### Domain Serviceはいつ使う？

- ルールID: `domain-service`
- 判断: 重要なドメイン操作が一つのEntityやValue Objectに自然に属さない場合に限って使う。
- 意味: Domain Serviceは、重要な業務上の操作でありながら、特定のEntityやValue Objectの責務として自然に置けない振る舞いを表すドメインモデルの要素です。
- 理由: 無理に一つのEntityへ置くより意味が明確になりますが、使いすぎるとデータと振る舞いが分離した貧血ドメインモデルになります。

判断するときに確認すること

- 複数のドメインオブジェクトが関与する
- 操作自体がドメインの語彙である
- アプリケーションの手順やI/Oではない

良い例: ParticipationScheduleDomainServiceが、参加したい募集と参加確定済みの募集の開催時間が重なるか確認する。

避ける例: Orderの全ロジックをOrderServiceへ移し、Orderをgetter/setterだけにする。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/modeling/domain-service

## アプリケーション設計

### Application Serviceには何を書く？

- ルールID: `application-service`
- 判断: ユースケースの進行、入出力、認可、Repository呼び出し、Transaction境界を調整し、業務判断はドメインモデルへ委譲する。
- 意味: Application Serviceは、画面やAPIから受けた要求に対して、Repositoryやドメインモデルを必要な順番で呼び出し、参加申請などのユースケースを実行します。業務上の判断は、Entity、Value Object、Domain Serviceが行います。
- 理由: アプリケーションの手順とドメインルールを分けると、同じルールを別の入口から安全に再利用できます。

判断するときに確認すること

- 処理の順序を組み立てる
- 外部I/Oを調整する
- ドメインオブジェクトの操作を呼び出す

良い例: 参加したい募集を取得し、開催時間の重複を確認し、募集へ参加を申請して保存する。

避ける例: Application Service内のif文だけでキャンセル期限や状態遷移を判定する。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/application-design/application-service

### Application Serviceにinterfaceは必要？

- ルールID: `application-service-interface`
- 判断: 呼び出し口を実装クラスから分けたい場合に定義する。このガイドラインではinterfaceの先頭にIを付ける。
- 意味: Application Serviceのinterfaceは、ユースケースとして公開するメソッド、その入力、返却値を定義する呼び出し口です。DDDで必須ではありません。
- 理由: 画面やAPIが実装クラスではなくユースケースの呼び出し方へ依存できます。

判断するときに確認すること

- 公開するユースケースが明確である
- 入力と返却値を定義している
- 実装の処理内容を含めない

良い例: IRequestParticipationApplicationServiceをRequestParticipationApplicationServiceが実装する。

避ける例: 実装クラスと同じ内容のinterfaceを目的なく増やす。

例外・補足: 実装が一つで分離する利点がなければ、Application Serviceの公開メソッドを直接呼んでも問題ありません。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/application-design/application-service-interface

### Transactionはどこで開始する？

- ルールID: `transaction`
- 判断: 原則としてApplication Serviceの1ユースケースを境界に開始し、1つのAggregateを原子的に更新する。
- 意味: Transactionは、一つのユースケースで行うDBへの変更を、まとめて確定または取り消す仕組みです。
- 理由: ユースケースの成功・失敗とコミットを揃えつつ、ドメインをDBのTransaction APIから独立させられます。

判断するときに確認すること

- Application ServiceまたはUnit of Workが制御する
- ドメインモデルはTransaction APIを知らない
- 複数Aggregateの長いロックを避ける

良い例: 参加申請の開始時にTransactionを開き、変更した募集の保存に成功した場合だけ確定する。

避ける例: Entityのメソッド内でDB Transactionを開始し、外部API呼び出し中もロックを保持する。

例外・補足: 複数Aggregateやコンテキストにまたがる処理は、単一Transactionに押し込まずイベントやSagaによる結果整合性を検討します。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/application-design/transaction

## ドメインモデルの永続化

### RepositoryはAggregateごとに作る？

- ルールID: `repository`
- 判断: 永続化が必要なAggregate RootごとにRepositoryを用意し、内部Entity用のRepositoryは作らない。
- 意味: Repositoryは、DBから読み込んだデータをAggregateとして組み立て、変更したAggregateの内容をDBへ保存するための窓口です。
- 理由: 取得と保存の単位を整合性境界と揃えると、Aggregateを部分的に更新して不変条件を壊す経路を減らせます。

判断するときに確認すること

- 保存対象はAggregate Root
- ドメインで必要な取得操作だけを定義する
- テーブル単位のCRUDにしない

良い例: 募集用のRepositoryが募集Aggregateを取得・保存し、申込期限や参加者IDごとのRepositoryは作らない。

避ける例: すべてのDBテーブルに同じCRUD Repositoryを機械的に生成する。

例外・補足: 大量検索や集計などの読み取りは、更新用Repositoryと分けたQuery ServiceやRead Modelで扱えます。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/persistence/repository

### Repository interfaceはどこに置く？

- ルールID: `repository-interface`
- 判断: Repository interfaceは対象のAggregateと同じDomain側に置き、DBを使う実装はInfrastructure側に置く。
- 意味: Repository interfaceは、Aggregate Rootをどのように取得・保存できるかをDomain側で定義するものです。DBやORMを使った具体的な処理は含めません。
- 理由: Application ServiceがDBやORMではなく、Domain側で定義した取得・保存の方法へ依存できます。

判断するときに確認すること

- メソッド名がドメインの語彙になっている
- ORM型を公開しない
- 実装詳細への依存が内向きに入り込まない

良い例: domain側のIRecruitmentRepositoryを、Infrastructure側のRecruitmentRepositoryが実装する。

避ける例: domainがORMのBaseRepositoryやQueryBuilderを直接importする。

例外・補足: 読み取り専用の検索や集計は、更新用Repositoryとは分けてApplication側のQueryとして定義する場合があります。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/persistence/repository-interface

## DDD実践リファレンス

### Value Objectで空欄をどう表す？

- ルールID: `value-object-empty`
- 判断: 必須項目の空欄はエラーにする。任意項目が未設定ならValue Object自体がない状態とし、値がないことに業務上の意味があれば名前のある状態として表す。
- 意味: 入力途中の空欄は画面側で持ち、業務上有効な値だけをValue Objectとして表します。
- 理由: 空文字やnullを持つValue Objectを許すと、そのValue Objectが業務上有効な値なのか判断しにくくなるためです。

判断するときに確認すること

- 入力途中の状態か
- 必須項目か任意項目か
- 値がない理由を区別する必要があるか

良い例: 年齢制限が単なる任意項目ならAgeRangeValueObject | null、業務上の状態ならnone | rangeで表す。

避ける例: 年齢制限がない状態を、最小年齢と最大年齢がnullのValue Objectで表す。

例外・補足: 未回答と該当なしを区別する場合は、nullではなく名前のある状態として表します。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/implementation/value-object-empty

## AIの回答に含めること

- 今回扱う業務上の問題
- 確認できた業務ルール
- 仮定または未確認の事項
- 提案するドメインモデルと、それぞれの役割
- 各業務ルールを守る場所
- 必要な場合だけ、Application Service、Repository、Transactionの構成
- 最小限のコード例とテストする内容

## AIが避けること

- DDDだからという理由だけでEntity、Value Object、Domain Service、Repositoryを増やす。
- DBのテーブル構造から先にドメインモデルを決める。
- 業務ルールをApplication ServiceやControllerの条件分岐へ集める。
- 業務上の根拠なしにAggregateの範囲を広げる。
- ドメインエキスパートへの確認が必要な内容を、技術的な都合だけで決める。
