ページ一覧を開く

Repository interface

DDDでの役割

Repository interfaceには、ドメインモデルを使うために必要な取得・保存の方法を定義します。DBからどのように読み込むか、どのSQLで保存するかは定義しません。

Domain側が必要な取得・保存の方法を決め、Infrastructure側がDBを使って実装します。これにより、Application Serviceが特定のDBやORMに依存しないようにします。

参加申請に必要な方法を決める

フットサルNOWの参加申請では、募集IDから「募集」を取得する、その人の参加が確定している募集を取得する、変更した「募集」を保存する、という三つの方法が必要です。

Repository interfaceには、このユースケースで必要な方法だけを定義します。テーブルごとの登録・更新・削除を、一律に定義するものではありません。

コードで表す

このガイドラインでは、Repository interfaceの先頭にIを付け、実装クラスと見分けられるようにします。

// IRecruitmentRepository.ts
import type { RecruitmentEntity } from "./RecruitmentEntity";

// 「募集」の取得・保存に必要な方法を定義するRepository interface
export interface IRecruitmentRepository {
  // 募集IDから、一件の募集Aggregateを取得する
  findById(recruitmentId: string): Promise<RecruitmentEntity | null>;

  // 指定した参加者の、参加が確定している募集Aggregateを取得する
  findConfirmedByParticipant(
    participantId: string,
  ): Promise<readonly RecruitmentEntity[]>;

  // 変更した募集Aggregateを保存する
  save(recruitment: RecruitmentEntity): Promise<void>;
}

interfaceには、メソッド名、受け取る値、返す値だけを書きます。DB接続やデータ変換の処理は書きません。

Application Serviceがinterfaceを使う

Application Serviceは、DBを使う実装クラスではなく、Repository interfaceを受け取ります。

export class RequestParticipationApplicationService {
  // 募集の取得・保存に必要な方法だけに依存する
  private readonly recruitmentRepository: IRecruitmentRepository;

  constructor(input: {
    recruitmentRepository: IRecruitmentRepository;
  }) {
    this.recruitmentRepository = input.recruitmentRepository;
  }
}

Application Serviceから見えるのは、募集を取得・保存できることだけです。その処理でどのDBやORMを使うかは、Application Serviceから見えません。

DBを使う処理を実装する

Infrastructure側のRepositoryがinterfaceを実装し、DBからの読み込み、ドメインモデルへの組み立て、DBへの保存を行います。

// DBを使うRepositoryの実装
export class RecruitmentRepository implements IRecruitmentRepository {
  async findById(
    recruitmentId: string,
  ): Promise<RecruitmentEntity | null> {
    // DBからデータを読み込み、RecruitmentEntityとして組み立てて返す
  }

  async findConfirmedByParticipant(
    participantId: string,
  ): Promise<readonly RecruitmentEntity[]> {
    // 参加が確定している募集をDBから読み込み、一覧として返す
  }

  async save(recruitment: RecruitmentEntity): Promise<void> {
    // RecruitmentEntityの変更内容をDBへ保存する
  }
}

ディレクトリで表す

src/features/futsal/
├── domain/
│   └── recruitment/
│       ├── RecruitmentEntity.ts
│       └── IRecruitmentRepository.ts  ← Repository interface
├── application/
│   └── RequestParticipationApplicationService.ts
└── infrastructure/
    └── repository/
        └── RecruitmentRepository.ts   ← Repository

Repository interfaceは、対象のAggregateと同じDomain側に置きます。DBやORMを使う実装はInfrastructure側に置きます。

※ Repository interfaceを別ファイルにすることや、interface名の先頭にIを付けることは、DDDの必須ルールではありません。このガイドラインでは、実装との違いをファイル名で見分けられるようにしています。