ページ一覧を開く

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   ← Repository

Domain側には、必要な取得・保存の方法だけを定義します。DBやORMを使う具体的な処理はInfrastructure側に置きます。IRecruitmentRepositoryの内容は、次の「Repository interface」で説明します。