Transaction

Transactionとは

Transactionは、一つのユースケースで行うDBへの変更を、まとめて確定または取り消す仕組みです。すべての変更に成功した場合だけ確定し、途中で失敗した場合は、それまでの変更も取り消します。

どこからどこまでを一つのTransactionとして扱うか、その範囲を「Transaction境界」と呼びます。

具体例で整理する

例えば、フットサルNOWで参加申請を受け付けると、「募集」に参加者が追加され、その結果がDBへ保存されます。

保存の途中で失敗したときに、一部のデータだけが更新されると、募集と参加者の情報が合わなくなります。そのため、参加申請で行う変更を一つのTransactionとして扱い、まとめて確定または取り消します。

Application Serviceで範囲を決める

このガイドラインでは、Application Serviceが実行する一つのユースケースを、Transactionの基本的な範囲にします。

// 参加申請のユースケースを実行する
async execute(command: RequestParticipationCommand): Promise<void> {
  // この中で行うDBへの変更を、一つのTransactionとして扱う
  await this.transaction.run(async () => {
    // 参加したい募集を取得する
    const recruitment = await this.recruitmentRepository.findById(
      command.recruitmentId,
    );

    if (!recruitment) {
      throw new Error("募集が見つかりません");
    }

    // RecruitmentEntityが参加申請のルールを確認し、参加者を追加する
    recruitment.requestParticipation({
      requesterId: new ParticipantIdValueObject(command.participantId),
      now: command.requestedAt,
    });

    // 変更した募集をDBへ保存する
    await this.recruitmentRepository.save(recruitment);
  });
}

transaction.run()の内側が、この例のTransaction境界です。処理が最後まで成功すれば変更を確定し、例外が発生すれば変更を取り消します。

DDDでの役割分担

Application Serviceは、どの処理を一つのTransactionに含めるかを決めます。EntityやValue Objectは、Transactionの開始や確定を行いません。

DBを使ってTransactionを開始し、確定または取り消す具体的な処理は、Infrastructure側で実装します。これにより、ドメインモデルへDB固有の処理を持ち込まずに済みます。

Aggregateとの関係

このガイドラインでは、原則として一つのTransactionで一つのAggregateを変更します。参加申請では、「募集」のAggregateを取得し、ルールを確認して変更し、そのAggregateを保存します。

複数のAggregateを一つのTransactionで変更すると、処理の範囲や影響が大きくなります。複数のAggregateを扱う必要がある場合は、本当に同時に確定する必要があるかを確認します。