この記事はZOZO Advent Calendar 2025 シリーズ9の最終日の記事です、大トリ✌️
はじめに
本記事では、Dynamic Consistency Boundary(以後、DCBと記述する)について、できるだけ公式ドキュメントを引用しつつ、私の解釈も含めながら説明する。
想定読者: 集約やリポジトリなどのドメイン駆動設計の戦術的パターン、およびイベントソーシングに関する基礎知識があり、DCBへの興味もあるがよく知らないという方
DCBの出自
DCBは、Sara Pellegriniが提唱した概念である。Pellegriniは「Kill the Aggregate」というブログシリーズで、従来のイベントソーシングにおける集約パターンの限界を詳細に分析し、その解決策としてDCBを提案した。なお、公式サイトの仕様策定にはBastian Waidelichらも参加している。本記事で触れる「静的な境界による硬直性」「リファクタリングの困難さ」は、このシリーズで深く掘り下げられている。
なぜDCBが必要なのか?
"The idea of DCB started with the goal of killing the Aggregate"
(DCBのアイデアは、集約を倒すという目標から始まった)
https://dcb.events/topics/aggregates/
と随分と物騒な言葉がドキュメントにはあるが、集約を完全否定するわけではなく、DCB導入の最大の理由は柔軟性の獲得にあると私は感じた。具体的に言及されていた課題は以下の通りである。
従来の集約ベースでの整合性確保における課題
静的な境界による硬直性
集約は設計時点でその境界が決定される。従来のイベントソーシングでは、各イベントが特定の集約インスタンスに固定的に紐づくため、後から集約の分割や統合を行うにはイベントストア全体の書き換えが必要になる。この構造的な制約により、ドメインの理解が深まった段階でのリファクタリングが困難になりやすい。
複数集約の不変条件に対応困難
例えば、学生とコースというエンティティに対する以下のようなビジネスルールを満たす実装を考える。
- 1人の学生は最大10コースまでしか登録できない
- 1つのコースは5名までしか登録できない
この制約は「学生」と「コース」という2つのエンティティにまたがるため、従来のアプローチでは以下の2択を迫られる。
選択肢1: 別々の集約として定義し、Sagaパターンで調整する
- 「学生」と「コース」を別々の集約として定義し、Sagaで整合性を担保
- デメリット: 補正イベントの設計が複雑になる。また、結果整合性への妥協が必要になり、制約違反を後から検知・補正する運用になる
選択肢2: 巨大な集約に統合する
- 関連するエンティティを1つの集約にまとめて、トランザクション境界を広げる
- デメリット: 集約が肥大化し、スケーラビリティが低下する。また、ドメインモデルとしても不自然な構造になりやすい
DCBによる解決
DCBでは、コマンド(ユースケース)毎に必要なイベントだけをタグでクエリし、動的にDecision Modelを構築する。これにより以下が実現できる。
- 複数エンティティにまたがる制約をSagaを使わず単一操作内で検証可能
- 集約境界に縛られない柔軟な一貫性境界を実現
- Sagaパターンの複雑性を回避
DCBにおける整合性保証の仕組み
DCBでは、複数エンティティにまたがる制約を、結果整合性(Saga)ではなく単一のイベント追記操作で検証する。公式ドキュメントではこれを "selectively enforce consistency"(選択的に整合性を強制する)と表現している。
具体的なメカニズム:
-
複数タグを持つ単一イベント: 例えば
StudentSubscribedToCourseイベントにstudent:123とcourse:456の両方のタグを付与 - Decision Model構築時に両方のタグでクエリ: 学生の登録数とコースの空き状況に関係するイベントを選択して取得
- 楽観的ロック(failIfEventsMatch): イベント追記時に、両方のタグに関わるイベントが追加されていないかチェック
つまり「複数の集約を一度にコミットする」のではなく、複数エンティティに影響を与える単一イベントを、楽観的ロックで競合検出しながら追記することで、同一境界内での同期処理を実現している。
従来手法とDCBの比較
以下のフロー図は DCB Specification および Projections を参考にして作成した。
従来の集約ベース
ポイント:
- 集約という静的な構造が一貫性境界を決定
- 楽観的ロック: 集約を読み込んだ時点のバージョン番号を記憶し、保存時に「現在のバージョンが読み込み時と同じか」をチェック。別のトランザクションが先にイベントを追記していた場合(バージョンが進んでいた場合)は失敗し、再読み込み・再検証を促す
DCBアプローチ
Decision Modelは従来のAggregateに相当する概念である。公式ドキュメントでは「Aggregateという用語をこれ以上オーバーロードしないため、Decision Modelと呼ぶ」と説明されている。Projectionを動的に合成して構築される点が、静的な構造を持つAggregateとの違いである。
参考: https://dcb.events/topics/aggregates/
ポイント:
-
Queryによってイベントの範囲を動的に決定
-
EventTypes: イベントの種類でフィルタ(例:
CourseCreated,StudentSubscribed) -
Tags: ドメイン固有のメタデータでフィルタ(例:
student:123,course:456)
-
EventTypes: イベントの種類でフィルタ(例:
- Decision Model: Projectionを合成して構築し、ビジネスルールを評価
- 楽観的ロック: Decision Modelを構築した時点のSequence Positionを記憶し、イベント追記時に「そのPosition以降に、同じQueryに一致するイベントが追加されていないか」をチェック。別のトランザクションが先にイベントを追記していた場合は失敗し、再読み込み・再検証を促す
- 1つのイベントに複数のタグを付与可能(複数エンティティに影響)
比較まとめ
| 項目 | 従来の集約 | DCB |
|---|---|---|
| 境界の決定 | 設計時(静的) | 実行時(動的) |
| イベント取得 | AggregateId(集約ID)単位 | Tags + EventTypes |
| 楽観的ロック | バージョン番号 | failIfEventsMatch(Query) |
| 複数エンティティ制約 | Saga/結果整合性が必要 | 単一イベント(複数タグ)で同期的に検証可能 |
個人的に気になったポイントを深掘りする
DCBは凝集性を失わせる?
コマンド毎(≒ユースケース毎)にDecision Modelを定義するというアプローチは、一見するとトランザクションスクリプトに近いように思える。従来の集約では1つのクラスにビジネスルールが凝集していたが、DCBではコマンドハンドラー毎にProjectionを合成するため、ビジネスロジックが分散してしまうのではないか?
この点について公式ドキュメントを調査したところ、以下のように述べられていた。
"DCB does not dictate how you design your application. You're free to organize logic around entities if that better suits your workflow."
(DCBはアプリケーションの設計方法を規定しない。エンティティを中心にロジックを組織化することも自由である)
https://dcb.events/faq/
また、DCBの公式ドキュメントでは従来のRepositoryをDCB上で実装する例が紹介されている。この例では:
- 集約クラス内にビジネスロジックを保持したまま移行可能
- 変更が必要なのはリポジトリ層のみ(タグベースのクエリに置き換え)
複数エンティティにまたがる制約については、公式ドキュメントでは単一責任を持つ小さなProjection関数を組み合わせる設計が推奨されている。
これらの小さな純粋関数を組み合わせてDecision Modelを構築することで、複数エンティティにまたがるビジネスルールを表現する。
考察
上記の調査結果から、DCBはビジネスロジックの配置について中立であると解釈した。集約パターンを使い続けることも、別のアプローチを取ることも開発者の選択に委ねられている。
単一エンティティに閉じた制約であれば、従来通り集約クラスにビジネスロジックを凝集させたまま、DCBの恩恵(リファクタリングの柔軟性など)を享受できる。一方、複数エンティティにまたがる制約では、そもそも従来の集約パターンでは1つのクラスに凝集させること自体が困難だった。小さなProjection関数を組み合わせるアプローチは「凝集性の喪失」というより、従来は表現困難だった制約を明示的にモデル化する手段と捉えられる。
- 単一エンティティの制約 → 従来通り集約クラスに凝集可能
- 複数エンティティの制約 → Projectionsの合成で表現
後者は従来のアプローチでは「Sagaで結果整合性」か「巨大な集約に統合」という選択肢しかなかった。DCBは第三の選択肢として、ビジネスルール単位での凝集を可能にする。
ただし注意すべきは、ドメインモデリングを怠ってエンティティをDCBの動的境界で更新するだけであれば、それはトランザクションスクリプトやアクティブレコードで十分であり、むしろそちらの方がシンプルで良いということになりかねない。
DCBの恩恵を受けるためには、これまで通り静的な一貫性境界を意識したドメインモデリングをしっかり行うことが前提である。その上で、従来の手法では対応困難だった複数エンティティにまたがる制約や、リファクタリングの柔軟性が必要な場面でDCBの動的境界を活用する。この順序を忘れてはならないと感じた。
DCBはロック範囲が広がり、パフォーマンスが低下する?
「タグで動的にイベントをクエリする」と聞くと、ロック範囲が広がり、パフォーマンスが低下するのではないかという懸念が生じる。
イベントストリームの構造的な違い
DCBと従来の集約ベースアプローチでは、イベントストリームの構造が根本的に異なる。
従来の集約ベース:
- イベントは集約ID単位の複数のサブストリームに分割される
- 各ストリームは独立して並列処理可能
- 集約IDをパーティションキーとした水平スケーリングが容易
"Events are partitioned into sub Event Streams, with each Event being assigned to a single Stream"
https://dcb.events/topics/aggregates/
DCB:
- イベントは単一のグローバルストリームに格納される
- タグによるフィルタリングで論理的な境界を実現
- グローバルなSequence Positionに依存するため、物理的なパーティショニングは容易ではない
DCBでパーティショニングが容易でない理由について、公式ドキュメントでは以下のように説明されている:
"DCB guarantees consistency only inside the scope of the global Sequence Position. Thus, Events must be ordered to allow the conditional appending. As a result, it's not (easily) possible to partition Events."
https://dcb.events/faq/
パフォーマンスに関する公式見解
DCB公式FAQでは、パフォーマンスについて以下のように述べられている:
"The primary goal of DCB is not to improve performance, but to provide a way to selectively enforce consistency where it's actually needed."
(DCBの主目的はパフォーマンス向上ではなく、実際に必要な箇所で選択的に整合性を強制する方法を提供することである)
https://dcb.events/faq/
DCBが優位になるケース
一方で、DCB公式ドキュメントでは従来のアプローチの問題点として以下が挙げられている:
"if enforcing inventory constraints required locking all events for all products, then no two orders could be processed simultaneously anywhere in the system – even for completely different product categories."
(在庫制約を強制するためにすべての製品のすべてのイベントをロックする必要がある場合、まったく異なる製品カテゴリであっても、システム全体で2つの注文を同時に処理することはできなくなる)
https://dcb.events/topics/tags/
これは「集約設計の失敗」とも言えるが、従来手法では強い整合性が必要な場合には、それらを同一集約に含めざるを得ないという構造的な問題がある。たとえ発生頻度が低いユースケースであっても、強い整合性を保つためには同じ集約に統合するしかなく、結果として巨大な集約が誕生しパフォーマンス悪化につながる。
DCBはこの問題に対し、タグベースの動的境界で「必要な時だけ」整合性境界を広げるアプローチを提供する。
- 必要なイベントのみ取得: タグでフィルタリングすることで、関連するイベントのみを取得し、無関係なイベントは除外される
-
細粒度のロック:
failIfEventsMatchは指定したクエリにマッチするイベントのみをチェックするため、異なるタグへの書き込みは競合しない - インデックス最適化: タグは予測可能なパターンを持つため、イベントストアはストレージとインデックス戦略を最適化できる
スケーラビリティ特性の違い
DCBと従来手法はスケーラビリティの特性が異なる。どちらが優れているかではなく、トレードオフとして理解すべきである。
| 特性 | 従来の集約ベース | DCB |
|---|---|---|
| 水平分割 | 集約ID単位で容易 | グローバルシーケンスに依存するため困難 |
| 競合範囲 | 集約インスタンス単位 | クエリにマッチするタグ単位 |
| 巨大集約の回避 | 困難(制約を含めると肥大化) | 可能(動的境界で必要時のみ拡張) |
公式ドキュメントでは「DCBの主目的はパフォーマンス向上ではなく、必要な箇所で選択的に整合性を強制すること」と明言されている。
現時点では両者を同条件で比較したベンチマークは公開されていない。パフォーマンス要件が厳しい場合は、実際のワークロードでの検証が必要である。
考察
上記の特性を踏まえると、DCBと従来手法はそれぞれ異なるシナリオで優位性を持つ。
従来手法が優位なケース:
- 集約ID単位でストリームを分割でき、物理的なパーティショニング・シャーディングが容易
- 異なる集約インスタンスは完全に独立して処理でき、水平スケーリングの設計がシンプル
DCBが優位になりうるケース:
- 従来手法では同期的な制約検証が必要な場合、それを同一集約に含めざるを得ない
- たとえ発生頻度が低いユースケースであっても、同じ集約に統合するしかなく、結果として巨大な集約が誕生しパフォーマンス悪化につながる
- DCBはタグベースの動的境界で「必要な時だけ」整合性境界を広げるアプローチを提供する
つまり、DCBのパフォーマンス面でのメリットは「全体的なスループット向上」ではなく、「巨大集約を避けられることによる局所的な改善」 と理解すべきである。
また、DCB準拠のイベントストアが適切にインデックスを実装していることが前提である。タグベースのクエリを効率的に処理できるイベントストアの選択が重要になる。
おわりに
本記事では、Dynamic Consistency Boundary(DCB)の概念と、従来のイベントソーシングにおける集約パターンとの違いについて解説した。
DCBはまだ発展途上の概念ではあるが、イベントソーシングでシステムを運用していく上で柔軟性の獲得は非常に重要である。今後、DCBをサポートするフレームワークやイベントストアが増え、実プロダクトでも導入可能な水準まで成熟していくことを期待したい。
参考資料
- DCB公式サイト
- Kill the Aggregate(Sara Pellegrini)