🚀

DDD を活用した ECサイトのドメインモデル設計 ~オーケストレーション/コレオグラフィ+CQRS で集約を横断するデータ取得~

2024/12/24に公開

1. はじめに

Amazon のような大規模 EC サイトでは、購入フローに複数の機能 (Order, Payment, Inventory, Shipping など) が緊密に連携します。しかし、それらを1つの巨大モノリスにまとめると、変更や保守が極めて困難になりがちです。

この課題に対し、DDD (ドメイン駆動設計) ではドメインを「バウンデッドコンテキスト」に分割し、それぞれ独立したモデルを構築。さらに、イベント駆動で疎結合に連携することで、変更に強いアーキテクチャを目指します。

本記事では、以下のポイントを取り上げます。

  1. Order/Payment/Shipping/Inventory それぞれの集約(エンティティ)クラス図 (UML)
  2. エラーシナリオを含む状態遷移図
  3. オーケストレーション or コレオグラフィでの通信方法の概略
  4. CQRS を活用した読み取りモデル (Viewモデル) による、集約を横断するデータ取得
  5. シーケンス図にアプリケーションサービス層・メッセージブローカーを明示し、集約間が直接通信しない点を表現

2. バウンデッドコンテキスト

2.1 大まかな役割分担

  1. Order Context
    • ユーザーから注文を受け、注文情報( Order, OrderLine )の状態を管理
  2. Payment Context
    • 決済を承認/却下する(外部決済サービスとの連携含む)
  3. Inventory Context
    • 在庫数を確保/引当を行い、不足があればバックオーダーやキャンセルを通知
  4. Shipping Context
    • 出荷準備や配送状況の更新、最終的な配送完了を管理

各コンテキストは自律的にドメインモデル (Aggregate) を保持し、ドメインイベントなどを使って疎結合に協調します。


3. 集約ごとのクラス図 (UML)

3.1 Order Context のクラス図

  • Order.statusPendingConfirmedPaidShippedCanceled などを遷移
  • Backordered在庫不足などで後から発送するケースを表す追加ステータス

3.2 Payment Context のクラス図

  • Payment が集約ルートで、orderIdamount を保持。statusPendingApproved / Declined に遷移
  • 外部決済APIやカード情報などはドメインサービスや外部アダプタで扱うイメージ

3.3 Shipping Context のクラス図

  • Shipping エンティティが ReadyShippedDelivered (or Failed) を管理
  • 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 を明示
  • ドメインモデル(OrderAggPaymentAgg)間で直接呼び出すのではなく、サービス層 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)
    • PaymentServicePaymentDeclinedEvent をパブリッシュ → OrderService が購読して order.cancel()
    • 場合によっては「別カードで再決済」するフローをアプリケーションサービス層で用意しておく
  • 在庫不足 (Backordered)
    • InventoryServiceInventoryNotAvailableEvent をパブリッシュ → OrderService が購読して order.backorder()
    • 後日、在庫が補充されたら InventoryRestockedEvent を出して、OrderServiceorder.pay() に進めるなど
  • 出荷失敗 (ShippingStatus = Failed)
    • ShippingServicefail()ShippingFailedEventOrderServiceorder.cancel() or order.reship() といった補償パスを検討

いずれのシナリオも、アプリケーションサービス層 + ドメインイベントを中心に状態遷移を進め、CQRSの読み取りモデルにも反映させることで、一貫性と拡張性を両立できます。


9. まとめ

  1. バウンデッドコンテキストごとに独立した集約
    • Order, Payment, Inventory, Shipping のように区切り、モノリス化を防ぎつつ、ドメインロジックを明確化
  2. オーケストレーション or コレオグラフィ
    • 全体フローを1つのサービス (またはSaga) が制御するか、イベント駆動で自律的に進めるかを選択。大規模システムでは両者を組み合わせることも多い
  3. 状態遷移図+エラーシナリオを明記
    • 決済失敗、在庫不足、出荷失敗など含めて「誰がどこでイベントを発行し、どの集約をどう更新するか」を整理
  4. シーケンス図にアプリケーションサービス層やメッセージブローカーを明示
    • 集約同士が直接通信せず、疎結合を保つ。どのサービスがイベントをPublish/Subscribeして、どの集約メソッドを呼び出すかが可視化される
  5. CQRS の読み取りモデル (View) を活用
    • 複数コンテキストのデータを横断的に参照するには、ドメインイベントを購読して集約間データを集約したRead DBを構築。高いスケーラビリティや可用性を確保できる

DDD は複雑なビジネスロジックをドメインモデル (Aggregate) 中心に整理し、マイクロサービス+イベント基盤と組み合わせることでスケーラブルかつ拡張性の高いアーキテクチャを実現します。さらに、CQRS による読み取りモデルを導入すれば、集約を横断するデータ取得も高パフォーマンスかつ疎結合で運用可能になります。ぜひ、UML やステートマシン図を用いて可視化しながら、チーム全体でシステムデザインを検討してみてください。


参考文献

  • エリック・エヴァンス『ドメイン駆動設計』 (DDDの青本)
  • バーナード・ガワー『実践ドメイン駆動設計』
  • Vaughn Vernon 『Implementing Domain-Driven Design』
  • マーチン・ファウラー『Patterns of Enterprise Application Architecture』
  • Greg Young『CQRS Documents』

Discussion