Repository
DDDでの役割
DDDでは、DBのテーブル構造からドメインモデルを決めるのではなく、業務を理解してドメインモデルを作ります。
Repositoryは、DBから読み込んだデータをAggregateとして組み立て、変更したAggregateの内容をDBへ保存するための窓口です。
Application Serviceは、Aggregateの取得と保存をRepositoryに任せます。そのため、どのテーブルから読み込み、どのように保存するかを知る必要はありません。
具体例で整理する
フットサルNOWで、参加者が募集へ参加を申請した場合を考えます。
まず、Application Serviceは、参加したい募集を示す「募集ID」をRepositoryへ渡します。RepositoryはDBから必要なデータを読み込み、「募集」を表すEntityとして返します。
Application Serviceは、取得した「募集」に参加申請を依頼します。「募集」は、申込期限、二重申請、定員を確認し、参加できる場合は参加者を追加します。
最後に、Application Serviceは変更した「募集」をRepositoryへ渡します。Repositoryは、その変更内容をDBへ保存します。
Repositoryの単位
Repositoryが取得・保存する単位は、Aggregate Rootです。
フットサルNOWでは、募集用のRepositoryが「募集」を取得・保存します。
申込期限や参加者IDだけを個別に取得・保存するRepositoryは作りません。申込期限や参加者IDは「募集」の一部なので、募集用のRepositoryがまとめて取得・保存します。
コードで表す
参加申請のApplication Serviceから、Repositoryを次のように呼び出します。
// 募集IDを指定し、募集Aggregateを取得する
const recruitment = await this.recruitmentRepository.findById(
input.recruitmentId,
);
if (!recruitment) {
throw new Error("募集が見つかりません");
}
// RecruitmentEntityに参加申請を受け付けてもらう
recruitment.requestParticipation({
requesterId: new ParticipantIdValueObject(input.participantId),
now: input.now,
});
// 参加者が追加された募集Aggregateを保存する
await this.recruitmentRepository.save(recruitment);Application Serviceが呼ぶのは、findById()やsave()です。どのテーブルから取得し、どのSQLで保存するかはRepositoryの利用側へ見せません。
DBとの役割を分ける
DBには、募集と参加者が別々のテーブルとして保存される場合があります。Repositoryの実装は、それらのデータを読み込み、ドメインモデルであるRecruitmentEntityとして組み立て直します。
保存するときは反対に、RecruitmentEntityの内容をDBへ保存できる形に変換します。ドメインモデルは、テーブル名やORMの型を知る必要がありません。
ディレクトリで表す
src/features/futsal/
├── domain/
│ └── recruitment/
│ ├── RecruitmentEntity.ts
│ └── IRecruitmentRepository.ts ← Repository interface
└── infrastructure/
└── repository/
└── RecruitmentRepository.ts ← RepositoryDomain側には、必要な取得・保存の方法だけを定義します。DBやORMを使う具体的な処理はInfrastructure側に置きます。IRecruitmentRepositoryの内容は、次の「Repository interface」で説明します。