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』