1. はじめに
Amazon のような大規模 EC サイトでは、購入フローに複数の機能 (Order, Payment, Inventory, Shipping など) が緊密に連携します。しかし、それらを1つの巨大モノリスにまとめると、変更や保守が極めて困難になりがちです。
この課題に対し、DDD (ドメイン駆動設計) ではドメインを「バウンデッドコンテキスト」に分割し、それぞれ独立したモデルを構築。さらに、イベント駆動で疎結合に連携することで、変更に強いアーキテクチャを目指します。
本記事では、以下のポイントを取り上げます。
- Order/Payment/Shipping/Inventory それぞれの集約(エンティティ)クラス図 (UML)
- エラーシナリオを含む状態遷移図
- オーケストレーション or コレオグラフィでの通信方法の概略
- CQRS を活用した読み取りモデル (Viewモデル) による、集約を横断するデータ取得
- シーケンス図にアプリケーションサービス層・メッセージブローカーを明示し、集約間が直接通信しない点を表現
2. バウンデッドコンテキスト
2.1 大まかな役割分担
-
Order Context
- ユーザーから注文を受け、注文情報( Order, OrderLine )の状態を管理
-
Payment Context
- 決済を承認/却下する(外部決済サービスとの連携含む)
-
Inventory Context
- 在庫数を確保/引当を行い、不足があればバックオーダーやキャンセルを通知
-
Shipping Context
- 出荷準備や配送状況の更新、最終的な配送完了を管理
各コンテキストは自律的にドメインモデル (Aggregate) を保持し、ドメインイベントなどを使って疎結合に協調します。
3. 集約ごとのクラス図 (UML)
3.1 Order Context のクラス図
-
Order.status
はPending
→Confirmed
→Paid
→Shipped
→Canceled
などを遷移 -
Backordered
は在庫不足などで後から発送するケースを表す追加ステータス
3.2 Payment Context のクラス図
-
Payment
が集約ルートで、orderId
とamount
を保持。status
がPending
→Approved
/Declined
に遷移 - 外部決済APIやカード情報などはドメインサービスや外部アダプタで扱うイメージ
3.3 Shipping Context のクラス図
-
Shipping
エンティティがReady
→Shipped
→Delivered
(orFailed
) を管理 -
fail()
は宛先不明、破損、受取拒否などで出荷に失敗した場合
3.4 Inventory Context のクラス図
- 「特定商品IDの在庫数を管理する集約」を単純化した例
- 実際には倉庫やロケーション単位など、より複雑な構造が必要なケースが多い
4. 状態遷移図:エラーシナリオも含む
4.1 Order の状態遷移例
-
Confirmed
の段階で在庫不足ならBackordered
へ遷移可能 -
Backordered
から在庫入荷後にPaid
へ移るシナリオなどを想定
4.2 Payment の状態遷移例 (エラー含む)
-
Declined
になったらOrder
側がCanceled
にするか、別カードで再決済するかなど、追加フローを検討
4.3 Shipping の状態遷移例
-
Failed
は宛先不明や受取拒否など -
Delivered
は最終到達を示す
5. 集約間の通信方法:オーケストレーション or コレオグラフィ
5.1 オーケストレーション
- 指揮者 (OrderService / Sagaなど) が全体フローを制御し、各サービスに「支払い依頼」「在庫引当依頼」「出荷依頼」などを順に指示
- 成功/失敗の結果を受けて次のステップに進んだり、補償処理を実行
5.2 コレオグラフィ
- 各サービスがドメインイベントを発行 (Publish) し、他サービスが購読 (Subscribe) して反応する形
- 指揮者を置かず、イベントの連鎖でフローが進む
- 例:「OrderConfirmedEvent」が飛んできたら
PaymentService
が決済実行し、「PaymentApprovedEvent」を返す → それをOrderService
が受け取ってorder.pay()
など
実際の大規模システムでは、部分的に オーケストレーション を使いつつ一部は コレオグラフィ とするなど、ハイブリッドになることが多いです。
6. ドメインイベント連携シーケンス図:エラー想定+サービス層の明示
以下はコレオグラフィ的なイベント駆動に近い例ですが、オーケストレーションでもアプリケーションサービス層を介する点は同じです。
-
アプリケーションサービス層 (
OrderService
,PaymentService
) と EventBus を明示 - ドメインモデル(
OrderAgg
やPaymentAgg
)間で直接呼び出すのではなく、サービス層 or メッセージブローカーを介する
7. CQRS による読み取りモデル(View)の活用
7.1 なぜ読み取りモデルが必要?
- DDD では各コンテキスト(BC)が独立したドメインモデルを持つため、横断的にJOINしないほうが独立性・変更容易性を保ちやすい
- しかし、画面表示やレポートなどで「Order + Payment + Inventory + Shipping の状態を1画面に表示したい」など、複数集約をまたぐデータ取得のニーズがある
そこで、CQRS (Command Query Responsibility Segregation) パターンを導入し、書き込み (集約) と 読み取り (View) を分離します。
- ドメインイベントを購読して読み取り用データストアに投影 (Projection) し、複数の集約をまたぐ情報をまとめて保持
- フロントエンドや外部システムからの読み込み要求は、この読み取りモデル (Viewモデル) に対して行う。これにより、疎結合かつスケーラブルに横断的なデータを取得可能
7.2 アーキテクチャ図(マイクロサービス+CQRS の読み取りモデル)
- EventBus (Kafka, RabbitMQ, etc.) が各マイクロサービスのアプリケーション層からのドメインイベントを受信
-
EventView 側の
QSvc
がこれらのイベントを購読し、横断的に結合した読み取り用DBを更新 - Webアプリや他システムは、この Read DB を参照して「Order + Payment + Shipping 状況」を一括で取得できる
8. エラー・リトライシナリオをより詳しく
-
決済失敗 (Declined)
-
PaymentService
がPaymentDeclinedEvent
をパブリッシュ →OrderService
が購読してorder.cancel()
- 場合によっては「別カードで再決済」するフローをアプリケーションサービス層で用意しておく
-
-
在庫不足 (Backordered)
-
InventoryService
がInventoryNotAvailableEvent
をパブリッシュ →OrderService
が購読してorder.backorder()
- 後日、在庫が補充されたら
InventoryRestockedEvent
を出して、OrderService
がorder.pay()
に進めるなど
-
-
出荷失敗 (ShippingStatus = Failed)
-
ShippingService
でfail()
→ShippingFailedEvent
→OrderService
でorder.cancel()
ororder.reship()
といった補償パスを検討
-
いずれのシナリオも、アプリケーションサービス層 + ドメインイベントを中心に状態遷移を進め、CQRSの読み取りモデルにも反映させることで、一貫性と拡張性を両立できます。
9. まとめ
-
バウンデッドコンテキストごとに独立した集約
- Order, Payment, Inventory, Shipping のように区切り、モノリス化を防ぎつつ、ドメインロジックを明確化
-
オーケストレーション or コレオグラフィ
- 全体フローを1つのサービス (またはSaga) が制御するか、イベント駆動で自律的に進めるかを選択。大規模システムでは両者を組み合わせることも多い
-
状態遷移図+エラーシナリオを明記
- 決済失敗、在庫不足、出荷失敗など含めて「誰がどこでイベントを発行し、どの集約をどう更新するか」を整理
-
シーケンス図にアプリケーションサービス層やメッセージブローカーを明示
- 集約同士が直接通信せず、疎結合を保つ。どのサービスがイベントをPublish/Subscribeして、どの集約メソッドを呼び出すかが可視化される
-
CQRS の読み取りモデル (View) を活用
- 複数コンテキストのデータを横断的に参照するには、ドメインイベントを購読して集約間データを集約したRead DBを構築。高いスケーラビリティや可用性を確保できる
DDD は複雑なビジネスロジックをドメインモデル (Aggregate) 中心に整理し、マイクロサービス+イベント基盤と組み合わせることでスケーラブルかつ拡張性の高いアーキテクチャを実現します。さらに、CQRS による読み取りモデルを導入すれば、集約を横断するデータ取得も高パフォーマンスかつ疎結合で運用可能になります。ぜひ、UML やステートマシン図を用いて可視化しながら、チーム全体でシステムデザインを検討してみてください。
参考文献
- エリック・エヴァンス『ドメイン駆動設計』 (DDDの青本)
- バーナード・ガワー『実践ドメイン駆動設計』
- Vaughn Vernon 『Implementing Domain-Driven Design』
- マーチン・ファウラー『Patterns of Enterprise Application Architecture』
- Greg Young『CQRS Documents』