Commandはいつ使う?
Commandとは
Commandは、Application Serviceへ実行してほしいユースケースと、その実行に必要な値を表す型です。
RequestParticipationCommandという名前なら、「参加申請を実行してほしい」という要求だとコードから分かります。
CommandはApplication層に置きます。EntityやValue Objectなどのドメインモデルではなく、DDDで必ず使うものでもありません。
具体例で整理する
フットサルNOWの参加申請では、「募集ID」「参加者ID」「申請日時」をApplication Serviceへ渡します。これらを、参加申請という一つの要求を表すCommandにまとめます。
// RequestParticipationCommand.ts
// 参加申請の実行に必要な値
export type RequestParticipationCommand = {
readonly recruitmentId: string;
readonly participantId: string;
readonly requestedAt: Date;
};readonlyを付け、作った後にCommandの値を変更できないようにします。
APIからApplication Serviceへ渡す
APIはHTTPリクエストやログイン情報から必要な値を取り出し、Commandを作ってApplication Serviceへ渡します。
const command: RequestParticipationCommand = {
recruitmentId,
participantId: signedInUser.id,
requestedAt: new Date(),
};
await requestParticipationApplicationService.execute(command);Commandには、HTTPのRequestやResponseを入れません。API固有の形式をApplication層へ持ち込まないためです。
Application Serviceで受け取る
export interface IRequestParticipationApplicationService {
execute(command: RequestParticipationCommand): Promise<void>;
}
export class RequestParticipationApplicationService
implements IRequestParticipationApplicationService {
async execute(
command: RequestParticipationCommand,
): Promise<void> {
const recruitment = await this.recruitmentRepository.findById(
command.recruitmentId,
);
if (!recruitment) {
throw new Error("募集が見つかりません");
}
// 業務上の判断はドメインモデルへ任せる
recruitment.requestParticipation({
requesterId: new ParticipantIdValueObject(command.participantId),
now: command.requestedAt,
});
}
}Command自身は業務上の判断を行いません。Application ServiceがCommandの値を使ってドメインモデルを呼び出します。
Commandを使わなくてもよい場合
渡す値が一つだけで、型を分けても意図が分かりやすくならない場合は、Application Serviceの引数として直接受け取る方法でも問題ありません。
Commandという名前を付けることより、HTTPの入力とApplication Serviceの入力を分け、ユースケースの意図がコードから分かることが重要です。
ディレクトリで表す
src/features/futsal/
└── application/
├── RequestParticipationCommand.ts
├── IRequestParticipationApplicationService.ts
└── RequestParticipationApplicationService.ts