次の方法で共有


マイクロサービス アーキテクチャでの通信

ヒント

このコンテンツは、.NET Docs で入手できる、またはオフラインで読み取ることができる無料のダウンロード可能な PDF として入手できる、コンテナー化された .NET アプリケーションの電子ブックである .NET マイクロサービス アーキテクチャからの抜粋です。

コンテナー化された .NET アプリケーションの .NET マイクロサービス アーキテクチャの電子ブックの表紙サムネイル。

1 つのプロセスで実行されているモノリシック アプリケーションでは、コンポーネントは言語レベルのメソッドまたは関数呼び出しを使用して相互に呼び出します。 これらは、コード (たとえば、 new ClassName()) を使用してオブジェクトを作成する場合に強く結合できます。また、具象オブジェクト インスタンスではなく抽象化を参照して依存関係の挿入を使用している場合は、分離された方法で呼び出すことができます。 どちらの方法でも、オブジェクトは同じプロセス内で実行されます。 モノリシック アプリケーションからマイクロサービス ベースのアプリケーションに変更する場合の最大の課題は、通信メカニズムの変更です。 インプロセス メソッド呼び出しをサービスの RPC 呼び出しに直接変換することは、分散環境で効率的に機能しない、おしゃべりで非効率な通信を招きます。 分散システムを適切に設計する際の課題はよく知られており、「分散コンピューティングの誤謬」として知られる定理があります。これは、開発者がモノリシックから分散設計に移行する際によく間違えて行う前提を示しています。

ソリューションは 1 つではなく、いくつかのソリューションです。 1 つのソリューションには、ビジネス マイクロサービスを可能な限り分離することが含まれます。 次に、内部マイクロサービス間の非同期通信を使用し、オブジェクト間のプロセス内通信で一般的なきめ細かい通信を、より粗い通信に置き換えます。 これを行うには、呼び出しをグループ化し、複数の内部呼び出しの結果を集計するデータをクライアントに返します。

マイクロサービス ベースのアプリケーションは、複数のプロセスまたはサービスで実行される分散システムであり、通常は複数のサーバーまたはホスト間でも実行されます。 通常、各サービス インスタンスはプロセスです。 そのため、サービスは、各サービスの性質に応じて、HTTP、AMQP などのプロセス間通信プロトコル、または TCP などのバイナリ プロトコルを使用して対話する必要があります。

マイクロサービス コミュニティは、"スマート エンドポイントとダム パイプ" の理念を促進します。 このスローガンは、マイクロサービス間で可能な限り分離され、1 つのマイクロサービス内で可能な限りまとまりのある設計を奨励します。 前に説明したように、各マイクロサービスは独自のデータと独自のドメイン ロジックを所有します。 しかし、エンド ツー エンド アプリケーションを構成するマイクロサービスは、通常、一元化されたビジネス プロセス オーケストレーターではなく、WS-* や柔軟なイベントドリブン通信などの複雑なプロトコルではなく、REST 通信を使用して単に振り付けられます。

一般的に使用される 2 つのプロトコルは、リソース API を使用した HTTP 要求/応答 (ほとんどの場合) と、複数のマイクロサービス間で更新を通信するときの軽量な非同期メッセージングです。 これらの詳細については、次のセクションで説明します。

通信の種類

クライアントとサービスは、さまざまな種類の通信を通じて通信でき、それぞれが異なるシナリオと目標を対象とします。 最初は、これらの種類の通信を 2 つの軸に分類できます。

最初の軸では、プロトコルが同期か非同期かが定義されます。

  • 同期プロトコル。 HTTP は同期プロトコルです。 クライアントは要求を送信し、サービスからの応答を待機します。 これは、同期 (スレッドがブロックされている) または非同期 (スレッドがブロックされず、応答が最終的にコールバックに到達する) 可能性があるクライアント コードの実行とは無関係です。 ここで重要な点は、プロトコル (HTTP/HTTPS) が同期的であり、クライアント コードがタスクを続行できるのは、HTTP サーバーの応答を受信した場合のみです。

  • 非同期プロトコル。 AMQP (多くのオペレーティング システムやクラウド環境でサポートされているプロトコル) などの他のプロトコルでは、非同期メッセージが使用されます。 通常、クライアント コードまたはメッセージの送信者は応答を待機しません。 RabbitMQ キューまたはその他のメッセージ ブローカーにメッセージを送信するときと同じようにメッセージを送信するだけです。

2 番目の軸は、通信に 1 つの受信者または複数の受信者があるかどうかを定義します。

  • 1 つのレシーバー。 各要求は、1 つの受信者またはサービスによって処理される必要があります。 この通信の例として、 コマンド パターンがあります。

  • 複数のレシーバー。 各要求は、複数の受信側に対して 0 で処理できます。 この種類の通信は非同期である必要があります。 たとえば、イベント ドリブン アーキテクチャなどのパターンで使用されるパブリッシュ/サブスクライブ メカニズムがあります。 これは、イベントを介して複数のマイクロサービス間でデータ更新を伝達する場合のイベント バス インターフェイスまたはメッセージ ブローカーに基づいています。通常は、トピックとサブスクリプションを使用して、Service Bus または Azure Service Bus のような同様の成果物を介して実装されます。

マイクロサービス ベースのアプリケーションでは、多くの場合、これらの通信スタイルの組み合わせを使用します。 最も一般的な種類は、通常の Web API HTTP サービスを呼び出すときの HTTP/HTTPS などの同期プロトコルを使用した単一受信側通信です。 マイクロサービスでは、通常、マイクロサービス間の非同期通信にメッセージング プロトコルも使用されます。

これらの軸は、考えられる通信メカニズムを明確に把握するのに適していますが、マイクロサービスを構築する際の重要な問題ではありません。 マイクロサービスを統合する際の重要なポイントは、クライアント スレッド実行の非同期性でも、選択したプロトコルの非同期性でもありません。 重要 なのは、 次のセクションで説明するように、マイクロサービスの独立性を維持しながら、マイクロサービスを非同期的に統合できることです。

非同期マイクロサービス統合によってマイクロサービスの自律性が強制される

前述のように、マイクロサービス ベースのアプリケーションを構築する際の重要なポイントは、マイクロサービスを統合する方法です。 理想的には、内部マイクロサービス間の通信を最小限に抑えるようにする必要があります。 マイクロサービス間の通信が少ないほど、より優れています。 ただし、多くの場合、マイクロサービスを何らかの形で統合する必要があります。 これを行う必要がある場合、ここで重要なルールは、マイクロサービス間の通信を非同期にする必要があるということです。 つまり、特定のプロトコル (非同期メッセージングと同期 HTTP など) を使用する必要があるわけではありません。 マイクロサービス間の通信は、データを非同期的に伝達することによってのみ行う必要があることを意味しますが、初期サービスの HTTP 要求/応答操作の一部として他の内部マイクロサービスに依存しないようにしてください。

可能であれば、クエリに対してではなく、複数のマイクロサービス間の同期通信 (要求/応答) に依存しないでください。 各マイクロサービスの目標は、エンド ツー エンド アプリケーションの一部である他のサービスがダウンしている場合や異常な場合でも、クライアント コンシューマーが自律的に使用できるようにすることです。 クライアント アプリケーションに応答を提供するために、あるマイクロサービスから他のマイクロサービスへの呼び出し (データ クエリの HTTP 要求の実行など) を行う必要があると思われる場合は、一部のマイクロサービスで障害が発生しても回復性がないアーキテクチャがあります。

さらに、図 4-15 の最初の部分に示すように、HTTP 要求チェーンを使用して長い要求/応答サイクルを作成する場合など、マイクロサービス間に HTTP 依存関係があると、マイクロサービスが自律的になるだけでなく、そのチェーン内のサービスの 1 つがうまく機能しないとすぐにパフォーマンスが影響を受けます。

クエリ要求などのマイクロサービス間に同期依存関係を追加するほど、クライアント アプリの全体的な応答時間が悪くなります。

マイクロサービス間の 3 種類の通信を示す図。

図 4-15 マイクロサービス間の通信におけるアンチパターンとパターン

上の図に示すように、同期通信では、クライアント要求の処理中にマイクロサービス間で要求の "チェーン" が作成されます。 これはアンチパターンです。 非同期通信マイクロサービスでは、非同期メッセージまたは http ポーリングを使用して他のマイクロサービスと通信しますが、クライアント要求はすぐに処理されます。

マイクロサービスが別のマイクロサービスで追加のアクションを発生させる必要がある場合は、可能であれば、元のマイクロサービス要求と応答操作の一部として、そのアクションを同期的に実行しないでください。 代わりに、非同期的に実行します (非同期メッセージングまたは統合イベント、キューなどを使用)。 ただし、可能な限り、元の同期要求と応答操作の一部としてアクションを同期的に呼び出さないでください。

最後に(そしてマイクロサービスを構築するときにほとんどの問題が発生する場所です)、最初のマイクロサービスに他のマイクロサービスがもともと所有していたデータが必要な場合は、そのデータに対する同期要求に依存しないでください。 代わりに、最終的な整合性 (通常は、以降のセクションで説明するように統合イベントを使用) を使用して、そのデータ (必要な属性のみ) を初期サービスのデータベースにレプリケートまたは伝達します。

前述の「 各マイクロサービスのドメイン モデル境界の識別 」セクションで説明したように、複数のマイクロサービス間で一部のデータを複製することは正しくない設計ではありません。逆に、データをその追加ドメインまたは境界コンテキストの特定の言語または用語に変換できます。 たとえば、 eShopOnContainers アプリケーション では、 identity-api という名前のマイクロサービスがあり、 Userという名前のエンティティを持つユーザーのデータの大部分を担当します。 ただし、 Ordering マイクロサービス内にユーザーに関するデータを格納する必要がある場合は、 Buyerという名前の別のエンティティとして格納します。 Buyer エンティティは、元の User エンティティと同じ ID を共有しますが、ユーザー プロファイル全体ではなく、Ordering ドメインで必要な属性が少数しかない場合があります。

最終的な整合性を保つために、任意のプロトコルを使用して通信し、マイクロサービス間でデータを非同期的に伝達することができます。 前述のように、イベント バスまたはメッセージ ブローカーを使用して統合イベントを使用することも、代わりに他のサービスをポーリングして HTTP を使用することもできます。 これは問題ありません。 重要なルールは、マイクロサービス間に同期依存関係を作成しないことです。

以降のセクションでは、マイクロサービス ベースのアプリケーションで使用することを検討できる複数の通信スタイルについて説明します。

コミュニケーション スタイル

使用する通信の種類に応じて、通信に使用できる多くのプロトコルと選択肢があります。 同期要求/応答ベースの通信メカニズムを使用している場合、特に Docker ホストまたはマイクロサービス クラスターの外部でサービスを発行する場合は、HTTP や REST のアプローチなどのプロトコルが最も一般的です。 (Docker ホストまたはマイクロサービス クラスター内で) サービス間で内部的に通信する場合は、バイナリ形式の通信メカニズム (TCP とバイナリ形式を使用した WCF など) を使用することもできます。 または、AMQP などの非同期のメッセージ ベースの通信メカニズムを使用することもできます。

JSON や XML などの複数のメッセージ形式や、より効率的なバイナリ形式もあります。 選択したバイナリ形式が標準でない場合は、その形式を使用してサービスを公開することはおそらくお勧めできません。 マイクロサービス間の内部通信には、標準以外の形式を使用できます。 これは、Docker ホストまたはマイクロサービス クラスター (Docker オーケストレーターなど) 内のマイクロサービス間で通信するとき、またはマイクロサービスと通信する独自のクライアント アプリケーションの場合に行うことができます。

HTTP および REST を使用した要求/応答通信

クライアントが要求/応答通信を使用すると、サービスに要求が送信され、サービスによって要求が処理され、応答が返されます。 要求/応答通信は、クライアント アプリからリアルタイム UI (ライブ ユーザー インターフェイス) のデータを照会するのに特に適しています。 そのため、マイクロサービス アーキテクチャでは、図 4-16 に示すように、ほとんどのクエリでこの通信メカニズムを使用する可能性があります。

ライブクエリと更新の要求/応答通信を示す図。

図 4-16 HTTP 要求/応答通信の使用 (同期または非同期)

クライアントが要求/応答通信を使用する場合、応答は短時間 (通常は 1 秒未満、または最大で数秒) で到着することを前提としています。 遅延応答の場合は、 メッセージング パターンメッセージング テクノロジに基づいて非同期通信を実装する必要があります。これは、次のセクションで説明する別のアプローチです。

要求/応答通信の一般的なアーキテクチャ スタイルは REST です。 このアプローチは、 HTTP プロトコルに基づいて緊密に結合され、GET、POST、PUT などの HTTP 動詞を受け入れます。 REST は、サービスを作成するときに最も一般的に使用されるアーキテクチャ通信アプローチです。 コア Web API サービスを開発するときに REST サービス ASP.NET 実装できます。

インターフェイス定義言語として HTTP REST サービスを使用する場合は、追加の値があります。 たとえば、 Swagger メタデータ を使用してサービス API を記述する場合は、サービスを直接検出して使用できるクライアント スタブを生成するツールを使用できます。

その他のリソース

HTTP に基づくプッシュとリアルタイム通信

もう 1 つの可能性 (通常は REST とは異なる目的で) は、 ASP.NET SignalRWebSocket などのプロトコルなどの上位レベルのフレームワークとのリアルタイムおよび 1 対多通信です。

図 4-17 に示すように、リアルタイム HTTP 通信は、クライアントが新しいデータを要求するのをサーバーが待機するのではなく、データが使用可能になったときに、接続されたクライアントにコンテンツをプッシュするサーバー コードを持つことを意味します。

SignalR に基づくプッシュとリアルタイムの通信を示す図。

図 4-17 1 対多のリアルタイム非同期メッセージ通信

SignalR は、バックエンド サーバーからクライアントにコンテンツをプッシュするためのリアルタイム通信を実現する優れた方法です。 通信はリアルタイムであるため、クライアント アプリは変更をほぼ瞬時に表示します。 これは通常、多くの WebSocket 接続 (クライアントごとに 1 つ) を使用して、WebSocket などのプロトコルによって処理されます。 典型的な例は、サービスがスポーツ ゲームのスコアの変化を多くのクライアント Web アプリに同時に伝達する場合です。