4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

身の回りの困りごとを楽しく解決! by Works Human IntelligenceAdvent Calendar 2024

Day 21

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

Posted at

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』
4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?