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
しかし、それから私はいくつかの欠点を見ます:
もっと良い方法はありますか?
これを解決するには、2つの追加パッケージを作成します
SalesEvents(OrderPlaced)
BillingEvents(PaymentReceived)
Events
パッケージの代わりに。 SalesEvents
とBillingEvents
は相互に参照せず、Sales
とBilling
の両方が参照するため、ここでは循環依存関係が消えていることは明らかです。 SalesEvents
とBillingEvents
ですが、相互にではありません。
次に、このソリューションを、リストした欠点と比較してみましょう。
たとえば、Sales
とSalesEvents
は同じ境界付きコンテキストに属しているため、対応する各イベントはそのコンテキスト内にとどまります。
新しいマイクロサービスごとにますます多くのイベントを取得することによって「爆発」する「神」パッケージはもうありません。
追加サービスは、必要なイベントを正確に参照できます。
これにより、3つの問題すべてが解決されます。
これは "インターフェース分離の原則" とも呼ばれ、クラスだけでなくパッケージにも適用できます。
ビルドサイクルとランタイムサイクルの両方を解決するための可能なソリューションは、サービスのAPI設計を再考することです。
たとえば、Billingはリクエストに応じて支払いを収集するためのAPIを提供できます。これは、コマンドメッセージCollectPaymentを介して実装できます。 CollectPaymentは、PaymentReceivedと同様にBilling境界コンテキストに属します。
これで、循環依存がなくなりました。
Sales ---> Billing(CollectPayment,PaymentReceived)
しかし、注意してください:これは、振付からオーケストレーションへの移行です。これは、必ずしもそうとは限りませんが、他の小さなダムサービスを要求することによって、あまりにも多くのスマートゴッドサービスが過度に機能する傾向があります。
「イベント」は、瞬間的に発生するものであり、アクションを起こすものではありません。ここにはまだ言及していない「もの」が1つあります。Order。販売と請求は2つの関連するビジネスプロセスであり、どちらも注文のステータスを更新します。
Orderに何が起こるかを理解したいのであれば、1つのオブジェクト、つまりその1つのオブジェクトだけを見なければなりません。 whenに基づいて効果的に変更されるシステム全体に散在するロジックを見つけたくありません。
次のような2つのクライアント、SalesClientとBillingClientを公開できます。
[販売->販売クライアント] [請求->請求クライアント]
SalesClientとBillingClientは、他のユーザーも依存できる成果物として公開されます。
その後、BillingClientは、サブモジュールとしても、SalesClientにも依存するため、BillingClientに依存します。セールスについても同様です。
さまざまなクライアントのもう1つの利点は、さまざまなイベントで異なるはずのトピックまたはキューの詳細をクライアント自体に含めることができるため、さまざまなモジュール間で重複しないことです。
例えば。 OrderPlacedがORDERS_PLACEDトピックで公開される場合、その詳細はセールスクライアントにのみ入力でき、他のモジュールはトピックの詳細を複製せずにセールスクライアントに依存できます。
または、SalesClientに、キューイングメカニズム自体の実装を非表示にする抽象化されたメソッドがある場合はさらに優れています。