メッセージベースの非同期通信
ヒント
このコンテンツは eBook の「コンテナー化された .NET アプリケーションの .NET マイクロサービス アーキテクチャ」からの抜粋です。.NET Docs で閲覧できるほか、PDF として無料ダウンロードすると、オンラインで閲覧できます。
非同期メッセージ通信およびイベントドリブン通信は、複数のマイクロサービスとそれに関連するドメイン モデル間で変更を反映するときに重要です。 マイクロサービスと境界付けられたコンテキスト (BC) について前に説明したとおり、モデル (ユーザー、カスタマー、製品、アカウントなど) は、マイクロサービスまたは BC ごとに異なるものを意味することがあります。 つまり、変更が発生したとき、異なるモデル間で変更を調整する方法が必要になります。 解決策は、最終的な整合性と、非同期メッセージングに基づくイベントドリブン通信です。
メッセージングを使用するとき、プロセスはメッセージを非同期で交換して通信します。 クライアントは、サービスにメッセージを送信することでコマンドまたは要求を作成します。 サービスが返信する必要がある場合は、別のメッセージをクライアントに送信します。 メッセージベースの通信であるため、クライアントは、返信をすぐに受信しないこと、返信がまったくない場合もあると仮定します。
メッセージはヘッダー (ID 情報やセキュリティ情報などメタデータ) と本文で構成されます。 通常、メッセージは AMQP のような非同期プロトコルを介して送信されます。
マイクロサービス コミュニティにおいてこの種の通信で好まれるインフラストラクチャは、軽量メッセージ ブローカーです。これは、SOA で使用される大規模なブローカーやオーケストレーターとは異なります。 軽量メッセージ ブローカーの場合、インフラストラクチャは通常は "ダム" であり、RabbitMQ などのシンプルな実装、または Azure Service Bus のようなクラウドのスケーラブル サービス バスに対して、メッセージ ブローカーとしてのみ作動します。 このシナリオでは、"スマート" 思考のほとんどは、メッセージを生成および処理するエンドポイント、すなわちマイクロサービスに存在します。
できるだけ従うことをお勧めするもう 1 つのルールは、内部サービス間では非同期メッセージングのみを使用し、クライアント アプリからフロントエンド サービス (API ゲートウェイと第 1 レベルのマイクロサービス) では同期通信 (HTTPなど) のみを使用するということです。
非同期メッセージング通信には 2 つの種類があります。単一受信者メッセージベースの通信と複数受信者メッセージベースの通信です。 次のセクションでは、これらについて詳しく説明します。
単一受信者メッセージベースの通信
単一受信者メッセージベースの非同期通信とは、チャネルを読み取るコンシューマーの 1 つのみにメッセージを届ける 2 点間の通信があり、メッセージが 1 回しか処理されないことを意味します。 ただし、特殊な状況があります。 たとえば、障害から自動的に復旧しようとするクラウド システムでは、同じメッセージが複数回再送されることがあります。 ネットワークまたはその他の障害のために、クライアントはメッセージの送信を再試行できる必要があり、サーバーはべき等の処理を実装して、特定のメッセージを 1 回だけ処理する必要があります。
単一受信者メッセージベース通信は、非同期コマンドを 1 つのマイクロサービスから別のマイクロサービスに送信する際に特に適しています。図 4-18 にこのアプローチを示します。
いったんメッセージベース通信 (コマンドまたはイベントを含む) の送信を開始したら、メッセージベース通信と同期 HTTP 通信を混在させないようにしてください。
図 4-18. 非同期メッセージを受信する 1 つのマイクロサービス
クライアント アプリケーションからコマンドが届くと、それらのコマンドは HTTP 同期コマンドとして実装できます。 メッセージベースのコマンドは、より高いスケーラビリティが必要な場合、またはメッセージベースのビジネス プロセスを既に使用している場合に使います。
複数受信者メッセージベースの通信
より柔軟性の高いアプローチとして、パブリッシュ/サブスクライブ メカニズムを使用し、送信側からの通信を、追加のサブスクライバー マクロサービスまたは外部アプリケーションに届けることができます。 これは、送信サービスの開放/閉鎖原則に従うために役立ちます。 このようにすると、将来サブスクライバーを追加する際に送信側サービスを変更する必要がありません。
パブリッシュ/サブスクライブ通信を使用するとき、イベント バス インターフェイスを使用して、イベントをサブスクライバーにパブリッシュすることがあります。
非同期イベントドリブン通信
非同期イベントドリブン通信を使用すると、製品カタログ マイクロサービスでの価格変更のように、ドメインで何かが起きて他のマイクロサービスがそれを認識する必要があるとき、マイクロサービスが統合イベントをパブリッシュします。 他のマイクロサービスはイベントをサブスクライブしているので、非同期でイベントを受信できます。 これが発生すると、受信側が自らのドメイン エンティティを更新する場合があり、別の統合イベントのパブリッシュにつながる可能性があります。 この発行およびサブスクライブ システムは、イベント バスの実装を使って実行されます。 イベント バスは、イベントのサブスクライブとサブスクライブ解除、およびイベントのパブリッシュに必要な API を備えた抽象化またはインターフェイスとして設計できます。 また、イベント バスは、非同期通信とパブリッシュ/サブスクライブ モデルをサポートするメッセージング キューまたはサービス バスなど、インタープロセスとメッセージング ブローカーに基づいて 1 つ以上の実装を持つこともできます。
システムで統合イベントによって駆動される最終的な整合性が使用されている場合は、エンド ユーザーに対してこのアプローチを明らかにすることをお勧めします。 システムでは、SignalR やクライアントからのポーリング システムなど、統合イベントを模倣するアプローチを使用してはなりません。 エンド ユーザーおよび事業主は、最終的な整合性をシステムに明示的に組み込む必要があります。また、明示的に行う限り、多くのケースでこのアプローチによってビジネスに問題が生じないことを理解する必要があります。 ユーザーは何らかの結果がすぐに表示されることを期待することがあり、最終的な整合性ではそのようにならない場合があるため、このアプローチは重要です。
「分散データ管理に関する課題とソリューション」セクションで説明したように、統合イベントを使用すると、複数のマイクロサービスにまたがるビジネス タスクを実装できます。 このようにして、サービス間の最終的な整合性を実現します。 最終的に整合性のあるトランザクションは、分散アクションのコレクションで構成されます。 各アクションでは、関連するマイクロサービスがドメイン エンティティを更新し、もう 1 つの統合イベントをパブリッシュします。これにより、同一のエンドツーエンド ビジネス タスク内で次のアクションが発生します。
重要な点は、同じイベントをサブクライブしている複数のマイクロサービスに通信を行う必要があるということです。 このためには、イベントドリブン通信に基づいたパブリッシュ/サブスクライブ メッセージングを使用できます (図 4-19 を参照)。 このパブリッシュ/サブスクライブ メカニズムは、マイクロサービス アーキテクチャ専用ではありません。 これは、DDD において境界付けられたコンテキストが通信する方法、またはコマンド クエリ責務分離 (CQRS) アーキテクチャ パターンで書き込みデータベースを読み取りデータベースに反映する方法に似ています。 目的は、分散システム全体の複数のデータ ソース間で最終的な整合性を得ることです。
図 4-19 非同期イベントドリブン メッセージ通信
非同期イベントドリブン通信では、1 つのマイクロサービスがイベントをイベント バスに発行します。多くのマイクロサービスは、それにサブスクライブして、それに関する通知を受けたり、それを処理したりすることができます。 ご使用の実装によって、イベントドリブンのメッセージベース通信で使用されるプロトコルが決まります。 AMQP は、キューにある通信の信頼性を向上できます。
これらのイベントで共有するデータの量は、識別子だけであるか、ビジネス データのさまざまな要素も含まれているかにかかわらず、もう 1 つの重要な考慮事項です。 これらの考慮事項については、thin と fat の統合イベントに関するこのブログ記事で説明されています。
イベント バスを使用する場合は、関連する実装に基づき、クラスにおいて抽象化レベル (イベント バス インターフェイスなど) を使用することができます。RabbitMQ のようなメッセージ ブローカーまたは「Azure Service Bus with Topics」(Azure Service Bus トピック) で説明したサービス バスの API をコードで使用します。 または、NServiceBus、MassTransit、Brighter のような高レベルのサービス バスを使用して、イベント バスとパブリッシュ/サブスクライブ システムを統合することもできます。
運用システムでのメッセージング テクノロジに関する注意事項
抽象イベント バスの実装に使用できるメッセージング テクノロジにはさまざまなレベルがあります。 たとえば、RabbitMQ (メッセージング ブローカー転送) や Azure Service Bus のような製品は、NServiceBus、MassTransit、Brighter といった他の製品よりも下のレベルにあるため、こらの製品は RabbitMQ や Azure Service Bus の上で作動することができます。 アプリケーション レベルに高度な機能がどれだけあるかや、すぐに利用できるスケーラビリティがアプリケーションで必要かどうかによって、何を選択するかが決まります。 開発環境で概念実証のためのイベント バスを実装するだけの場合は、eShopOnContainers サンプルで行ったように、Docker コンテナーで実行する RabbitMQ 上のシンプルな実装で十分です。
ただし、非常に高いスケーラビリティが求められるミッションクリティカルな運用システムでは、Azure Service Bus を評価してもよいでしょう。 分散アプリケーションの開発を容易にする高レベルの抽象化と機能の場合には、NServiceBus、MassTransit、Brighter など市販やオープンソースのサービス バスも評価することをお勧めします。 もちろん、RabbitMQ や Docker など低レベル テクノロジの上に独自のサービスバス機能を構築することもできます。 ただし、そのような組み込み作業はカスタム エンタープライズ アプリケーションにとってコストがかかりすぎる可能性があります。
イベント バスに対する弾力的なパブリッシュ
複数のマイクロサービスにイベントドリブン アーキテクチャを実装する際の課題は、関連する統合イベントをイベント バスに弾力的にパブリッシュする一方、なんらかの方法でトランザクションに合わせて、どうやって元のマイクロサービスの状態をアトミックに更新するかということです。 この機能を実現するいくつかの方法を次に示しますが、他のアプローチも考えられます。
MSMQ などのトランザクション (DTC ベース) キューを使用します。 (ただし、これは従来のアプローチです。)
トランザクション ログ マイニングを使用します。
完全なイベント ソーシング パターンを使用します。
送信トレイ パターンを使用します。これは、イベントを作成してパブリッシュするイベントクリエーター コンポーネントの基盤となる、メッセージ キューとしてのトランザクション データベース テーブルです。
不適切なデータを含むメッセージの発行方法など、この領域の課題の詳細については、「Azure でのミッション クリティカルなワークロードのデータ プラットフォーム: すべてのメッセージを処理する必要があります」を参照してください。
非同期通信を使用する際に考慮する必要がある他のトピックは、メッセージのべき等性とメッセージの重複除去です。 これらのトピックについては、このガイドで後から説明する「マイクロサービス間でイベント ベースの通信を実装する (統合イベント)」をご覧ください。
その他の技術情報
イベント駆動型メッセージング
https://patterns.arcitura.com/soa-patterns/design_patterns/event_driven_messagingチャンネルの発行/サブスクライブ
https://www.enterpriseintegrationpatterns.com/patterns/messaging/PublishSubscribeChannel.htmlUdi Dahan。 CQRS の明確化
https://udidahan.com/2009/12/09/clarified-cqrs/コマンド クエリ責務分離 (CQRS)
https://learn.microsoft.com/azure/architecture/patterns/cqrs境界コンテキスト間の通信
https://learn.microsoft.com/previous-versions/msp-n-p/jj591572(v=pandp.10)Jimmy Bogard。 復元性を目指したリファクタリング: 結合の評価
https://jimmybogard.com/refactoring-towards-resilience-evaluating-coupling/
.NET