DDDにおける整合性境界のベストプラクティス

この質問をもらったのですが、かなり長くなってしまったので、ブログ記事にしました。今年一発目のブログ記事〜!

質問者さんは、よい視点をお持ちですね。

ちょっと前のポストにも関係しそう。

結論から言うと、“強い整合性” を保ちたい範囲は集約 (Aggregate) の境界で考え、複数の集約間で発生する “弱い整合性” はユースケースやサーガ (Saga)・プロセスマネージャーなどで扱う、というのがDDDの典型的なアプローチです。(特にマイクロサービス文脈やRDBMS以外のストレージを混ぜて使う場合にこのような方法を取ることが多いです)

強い整合性と集約の境界

ここでいう「強い整合性」は、RDBMS等のACIDトランザクションで一貫性を保ちたい範囲を指します(RDBMS以外であっても、ACIDほど強いトランザクションがなかったりしますが、似たような考え方になると思います)。

DDDにおいて集約は「整合性を保つための最小単位」と定義されるため、1つの集約を更新するときはその内部だけで整合性が完結するように、同一トランザクション内で処理するのが基本的な設計方針です。

もし「複数の集約に対してどうしても同時に強い整合性が必要」という状況なら、それらを1つの集約として再設計できないか検討することがDDDの前提として推奨されます。

どうしても複数集約にまたがる一括トランザクションが必要なら、分散トランザクションなどかなり複雑な実装が必要になるため、設計の見直しを含めて慎重に判断することになります。

弱い整合性とユースケース/サーガ

一方で「複数の集約にまたがる操作をまとめて処理したい」「最終的に全体の整合性が取れればよい」というケースは珍しくありません。

この場合はユースケースやサーガ(ここではプロセスマネージャーも含む)などのレイヤーで責任を持たせ、イベントやメッセージ駆動で “最終的に整合” させるのが一般的なやり方です。

これを「緩やかな (弱い) トランザクション」と呼ぶこともありますが、要するに「即時にACIDを保証するのではなく、段階的・非同期的に整合性を担保していく」アプローチです。(サーガのアプローチはACIDの “I” がない、または弱いという見方が正しくて、その代わりに最終的に矛盾がなくなる (Eventual Consistency)という考え方になります)

あくまで集約の境界は壊さず、各集約はそれぞれ独立して強い整合性を保ちつつ、結果として複数集約間の状態が矛盾なく落ち着くように設計します。

ユースケースを “そのまま” 強いトランザクション境界としない理由

「ユースケース = トランザクション境界」とすると、一見シンプルに見えますが、複数の集約を1つの大きなトランザクションに押し込めてしまうリスクが生じます。

これはDDDが大切にする「集約ごとに強い整合性を保つ」というルールを破りやすい構造になるため、推奨されません。

  • ユースケースはビジネスロジックやアプリケーションフローの単位であり、必ずしも「1つの集約だけを扱う」わけではない。

  • そのため “ユースケースを強いトランザクション境界にする” と、意図せず複数集約が1つのトランザクションに巻き込まれる可能性がある。

したがって、ユースケースの責務は複数の集約を協調させること (弱い整合性) であり、1つの集約を更新するときには集約内部でトランザクションを完結させる、という切り分けが望ましいのです。

まとめ

  • 強い整合性 (ACID) が必要な範囲 = 集約の境界

    • RDBMSトランザクションを張るなら、1集約あたりを単位とするのがDDDの基本。

  • 複数集約をまたぐ “弱い整合性” の扱い = ユースケースやサーガなど

    • 同時コミットが必要なら集約定義の見直しを、そうでなければイベント駆動やサガパターンを使う。

ですので「ユースケースを強いトランザクション境界とするな!」は、“ユースケース全体を1つのACIDトランザクションにしてしまわないように” という主旨だと捉えるとわかりやすいかと思います。

実際には、「ユースケースが複数の集約を扱う場合は弱い整合性を意識し、強い整合性が必要なところだけを集約内トランザクションで守る」 という設計がDDDの考え方にうまく合致します。 (エヴァンス本にはここまで言及されていないので、これは様々な書籍や分散システムでの開発経験から導き出した私見です)

@j5ik2o
歳をとっても霜降り肉!をモットーにしております