# 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`
- Commandの型名とファイル名には `Command` を付ける。例: `RequestParticipationCommand`
- 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

## API設計

### APIには何を書く？

- ルールID: `api`
- 判断: HTTPに関する入力と出力を扱い、ユースケースはApplication Serviceへ任せ、業務上の判断はAPIに書かない。
- 意味: APIはPresentation層の一部です。外部からリクエストを受け取り、Application Serviceを呼び出し、その結果をHTTPレスポンスとして返します。
- 理由: HTTPの都合と業務ルールを分けることで、同じユースケースを別のAPIや画面からも利用しやすくなります。

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

- リクエストから必要な値を取り出す
- ログイン中の利用者を確認する
- Application Serviceを呼ぶ
- 結果をHTTPレスポンスへ変換する

良い例: 参加申請APIが募集IDとログイン中の参加者IDをApplication Serviceへ渡し、処理結果をHTTPレスポンスとして返す。

避ける例: APIが申込期限、二重申請、定員を直接確認し、Repositoryへ参加者を保存する。

例外・補足: 必須項目やデータ形式など、リクエストとして受け取れる形かを確認する処理はAPIで行えます。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/presentation/api

## DDD実践リファレンス

### Commandはいつ使う？

- ルールID: `command`
- 判断: Application Serviceへ複数の値を渡す場合や、要求の意図を型名で明確にしたい場合にCommandを使う。
- 意味: Commandは、Application Serviceへ実行してほしいユースケースと、その実行に必要な値を表す入力用の型です。DDDで必須の要素ではありません。
- 理由: HTTPの入力や汎用的なInputではなく、実行したいユースケースの名前を付けることで、コード上でも要求の意図が分かりやすくなります。

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

- 一つのユースケースへの要求を表す
- Application層に置く
- 業務上の判断や処理を持たない
- HTTPやORM固有の型を含めない

良い例: RequestParticipationCommandが、参加申請に必要な募集ID、参加者ID、申請日時を変更できない値として持つ。

避ける例: APIのRequestオブジェクトやRecruitmentEntityを、そのままApplication Serviceへ渡す。

例外・補足: 渡す値が一つだけで型を分ける利点がなければ、Application Serviceの引数として直接受け取る方法でも問題ありません。

人向けの説明: https://becauseiadmire.com/ddd/guidelines/implementation/command

### Repositoryは何を返す？

- ルールID: `repository-return-value`
- 判断: 業務上の変更を行う場合はRepositoryからAggregate Rootを取得する。一覧・検索・集計用のデータはQueryで取得し、用途に合うRead Modelを返す。Read ModelとQuery interfaceはApplication層、DB行やORM固有の型とDAOはInfrastructure層に置く。
- 意味: 更新用のRepositoryは、DBのデータをドメインモデルとして組み立て、基本的にAggregate Rootを返します。
- 理由: 更新では、現在の状態と振る舞いを持つAggregate Rootを取得することで業務ルールを実行できます。読み取りでは業務上の変更を行わないため、必要な項目だけを持つRead Modelを返せます。

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

- 取得後に業務ルールを使って変更するか
- Aggregate全体の整合性が必要か
- 一覧・検索・集計の表示専用か
- DBやORM固有の型が外側へ漏れていないか

良い例: 参加申請ではRepositoryがRecruitmentEntityを返す。募集一覧ではApplication層のIRecruitmentQueryがRecruitmentListItemを返し、Infrastructure層のQuery実装がDAOのDB行をRead Modelへ変換する。

避ける例: Repositoryが画面ごとに異なるDB行やORMモデルを返し、Application ServiceがDBの構造へ依存する。

例外・補足: 存在確認や件数確認だけが必要な場合は、Repositoryがbooleanやnumberを返すメソッドを定義することもあります。単純な読み取りでEntityをそのまま使える場合は、Queryを別に作らなくても構いません。

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

### Repositoryへ何を渡す？

- ルールID: `repository-argument`
- 判断: 業務上の変更はAggregate Rootのメソッドで行い、saveには変更後のAggregate Rootを渡す。変更項目だけを渡してRepositoryに業務上の判断を行わせない。
- 意味: Repositoryへ渡すものはメソッドによって異なります。取得には識別子や検索条件を渡し、保存には変更後のAggregate Rootを渡します。
- 理由: 変更する項目が一つでも、Aggregate Rootを通すことで業務ルールを守れます。Aggregate Rootをsaveへ渡すことと、DBの全項目を更新することは別です。

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

- 取得には識別子や検索条件を渡しているか
- saveには変更後のAggregate Rootを渡しているか
- 業務上の変更をAggregate Rootが担当しているか
- 一部更新のためにAggregateの範囲が広くなりすぎていないか

良い例: 募集を取得し、RecruitmentEntity.changeLocation()で開催場所を変更してから、変更後のRecruitmentEntityをsaveへ渡す。

避ける例: save(recruitmentId, newLocation)へ変更項目だけを渡し、Repositoryが開催場所を変更できるか判断する。

例外・補足: 存在確認には識別子、削除には設計に応じて識別子またはAggregate Rootを渡すことがあります。RepositoryやORMは、Aggregate Rootを受け取っても変更されたDB項目だけを更新できます。

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

### 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

### DDDで例外をどう扱う？

- ルールID: `domain-exception`
- 判断: 業務ルール違反はEntity、Value Object、Domain Serviceから、対象が見つからない場合はApplication Serviceから出す。Infrastructure固有の例外やHTTPの情報をDomain層へ持ち込まない。
- 意味: 例外は、その失敗を判断した層から出します。DDDは例外の使用を必須にしておらず、通常起こり得る結果はResult型やNotificationで表す方法もあります。
- 理由: 失敗を判断する責任と例外を出す場所を揃えると、業務ルール、ユースケース、DB、HTTPの関心を分離できます。状態を変更する前にルールを確認すれば、例外が発生しても不正な状態が残りません。

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

- 失敗を判断できる層はどこか
- 例外を出す前に状態を変更していないか
- 通常起こり得る結果として分岐する必要があるか
- Domain層の例外にHTTPやORMの情報が含まれていないか

良い例: RecruitmentEntityが申込期限を確認し、期限切れならParticipationDeadlineExceededErrorを出す。APIがその例外をHTTP 409へ変換する。

避ける例: Domain層の例外がHTTPステータスを持つ。またはAPIが募集の申込期限を直接確認して例外を出す。

例外・補足: 期限切れや定員到達が通常の結果として頻繁に扱われる場合や、複数の入力エラーをまとめて返す場合は、例外ではなくResult型やNotificationを使えます。

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

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

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

## AIが避けること

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