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 ← RepositoryRepository interfaceは、対象のAggregateと同じDomain側に置きます。DBやORMを使う実装はInfrastructure側に置きます。
※ Repository interfaceを別ファイルにすることや、interface名の先頭にIを付けることは、DDDの必須ルールではありません。このガイドラインでは、実装との違いをファイル名で見分けられるようにしています。