web-dev-qa-db-ja.com

イベント駆動型システムの循環依存関係を解決するにはどうすればよいですか?

2つのサービス(DDDによる制限付きコンテキスト)を検討してください。

  • 売上高
  • 課金

セールスは、支払いを処理するための注文と請求の作成を担当します。

販売は注文を追跡し、請求は支払いを保留します。

 Sales DB                 Billing DB
+----------+-------+     +------------+----------+-------+
| order_id | paid  |     | payment_id | order_id | total |
+----------+-------+     +------------+----------+-------+
| 123      | true  |     | 456        | 123      | 789.5 |
+----------+-------+     +------------+----------+-------+

アクションが完了すると、イベントが公開されます。

  • 売上高
    • 注文済み
  • 請求
    • お支払い頂いた

OrderPlacedが受信された後に請求が支払いを収集し、PaymentReceivedが来たときにセールスが注文状態を更新します。

これにより、販売と請求の間に循環依存関係が作成されます。

Sales(OrderPlaced) <---> Billing(PaymentReceived)

これにより、個別のアーティファクト(JARなど)でサービスを構築することが不可能になります。

この背後にあるアイデアは、独立してデプロイ可能なアーティファクトを作成することです。これは、後でアプリケーションに統合できます。

Application.jar (-> Sales.jar, -> Billing.jar)
WebApp.jar (-> Sales.jar, -> Billing.jar)
StandaloneApp.jar (-> Sales.jar)

可能な解決策は、テクニカルカットパッケージイベントを作成することです。

  • 売上高
  • 課金
  • イベント
    • 注文済み
    • お支払い頂いた

両方のイベントクラスをその中に入れ、サービスがそれに依存するようにします。

Sales ---> Events(OrderPlaced,PaymentReceived) <--- Billing

しかし、それから私はいくつかの欠点を見ます:

  1. ドメインイベントはドメインコンテキストを離れます。
  2. パッケージのイベントは簡単に爆発する可能性があります。
  3. イベントに依存する追加のサービスは、潜在的に必要とする以上のものに依存しています。

もっと良い方法はありますか?

2
Barney

これを解決するには、2つの追加パッケージを作成します

   SalesEvents(OrderPlaced)

   BillingEvents(PaymentReceived)

Eventsパッケージの代わりに。 SalesEventsBillingEventsは相互に参照せず、SalesBillingの両方が参照するため、ここでは循環依存関係が消えていることは明らかです。 SalesEventsBillingEventsですが、相互にではありません。

次に、このソリューションを、リストした欠点と比較してみましょう。

  1. たとえば、SalesSalesEventsは同じ境界付きコンテキストに属しているため、対応する各イベントはそのコンテキスト内にとどまります。

  2. 新しいマイクロサービスごとにますます多くのイベントを取得することによって「爆発」する「神」パッケージはもうありません。

  3. 追加サービスは、必要なイベントを正確に参照できます。

これにより、3つの問題すべてが解決されます。

これは "インターフェース分離の原則" とも呼ばれ、クラスだけでなくパッケージにも適用できます。

6
Doc Brown

ビルドサイクルとランタイムサイクルの両方を解決するための可能なソリューションは、サービスのAPI設計を再考することです。

たとえば、Billingはリクエストに応じて支払いを収集するためのAPIを提供できます。これは、コマンドメッセージCollectPaymentを介して実装できます。 CollectPaymentは、PaymentReceivedと同様にBilling境界コンテキストに属します。

  • 売上高
    • 注文済み
  • 請求
    • CollectPayment
    • お支払い頂いた

これで、循環依存がなくなりました。

Sales ---> Billing(CollectPayment,PaymentReceived)

しかし、注意してください:これは、振付からオーケストレーションへの移行です。これは、必ずしもそうとは限りませんが、他の小さなダムサービスを要求することによって、あまりにも多くのスマートゴッドサービスが過度に機能する傾向があります。

1
ttulka

「イベント」は、瞬間的に発生するものであり、アクションを起こすものではありません。ここにはまだ言及していない「もの」が1つあります。Order。販売と請求は2つの関連するビジネスプロセスであり、どちらも注文のステータスを更新します。

Orderに何が起こるかを理解したいのであれば、1つのオブジェクト、つまりその1つのオブジェクトだけを見なければなりません。 whenに基づいて効果的に変更されるシステム全体に散在するロジックを見つけたくありません。

0
Mike Robinson

次のような2つのクライアント、SalesClientとBillingClientを公開できます。

[販売->販売クライアント] [請求->請求クライアント]

SalesClientとBillingClientは、他のユーザーも依存できる成果物として公開されます。

その後、BillingClientは、サブモジュールとしても、SalesClientにも依存するため、BillingClientに依存します。セールスについても同様です。

さまざまなクライアントのもう1つの利点は、さまざまなイベントで異なるはずのトピックまたはキューの詳細をクライアント自体に含めることができるため、さまざまなモジュール間で重複しないことです。

例えば。 OrderPlacedがORDERS_PLACEDトピックで公開される場合、その詳細はセールスクライアントにのみ入力でき、他のモジュールはトピックの詳細を複製せずにセールスクライアントに依存できます。

または、SalesClientに、キューイングメカニズム自体の実装を非表示にする抽象化されたメソッドがある場合はさらに優れています。

0
Nils