次の方法で共有


マイクロサービス指向アプリケーションを設計する

ヒント

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

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

このセクションでは、架空のサーバー側エンタープライズ アプリケーションの開発に焦点を当てます。

アプリケーションの仕様

架空のアプリケーションは、ビジネス ロジックを実行し、データベースにアクセスし、HTML、JSON、または XML 応答を返すことによって要求を処理します。 アプリケーションは、シングル ページ アプリケーション (SPA) を実行しているデスクトップ ブラウザー、従来の Web アプリ、モバイル Web アプリ、ネイティブ モバイル アプリなど、さまざまなクライアントをサポートする必要があると言います。 アプリケーションは、サード パーティが使用する API を公開する場合もあります。 また、マイクロサービスまたは外部アプリケーションを非同期的に統合できるため、部分的な障害が発生した場合のマイクロサービスの回復性に役立ちます。

アプリケーションは、次の種類のコンポーネントで構成されます。

  • プレゼンテーション コンポーネント。 これらのコンポーネントは、UI の処理とリモート サービスの使用を担当します。

  • ドメインまたはビジネス ロジック。 このコンポーネントは、アプリケーションのドメイン ロジックです。

  • データベース アクセス ロジック。 このコンポーネントは、データベース (SQL または NoSQL) へのアクセスを担当するデータ アクセス コンポーネントで構成されます。

  • アプリケーション統合ロジック。 このコンポーネントには、メッセージ ブローカーに基づくメッセージング チャネルが含まれます。

アプリケーションでは高いスケーラビリティが必要ですが、一部のサブシステムでは他のサブシステムよりもスケーラビリティが必要になるため、垂直サブシステムを自律的にスケールアウトできます。

アプリケーションは、複数のインフラストラクチャ環境 (複数のパブリック クラウドとオンプレミス) にデプロイできる必要があり、理想的にはクロスプラットフォームであり、Linux から Windows (またはその逆) に簡単に移行できる必要があります。

開発チームのコンテキスト

また、アプリケーションの開発プロセスについては、次のことを前提としています。

  • アプリケーションの異なるビジネス領域に焦点を当てた複数の開発チームがいます。

  • 新しいチーム メンバーは、迅速に生産性を高め、アプリケーションを理解し、変更しやすいものにする必要があります。

  • アプリケーションは、長期的な進化と絶え間なく変化するビジネス ルールを持つことになります。

  • 優れた長期的な保守性が必要です。つまり、将来新しい変更を実装する際に機敏性を備えながら、他のサブシステムに最小限の影響を与えながら複数のサブシステムを更新できます。

  • アプリケーションの継続的インテグレーションと継続的デプロイを実践したいと考えています。

  • アプリケーションを進化させながら、新しいテクノロジ (フレームワーク、プログラミング言語など) を利用したいと考えています。 新しいテクノロジに移行するときにアプリケーションを完全に移行したくないのは、コストが高く、アプリケーションの予測可能性と安定性に影響を与えるためです。

アーキテクチャの選択

アプリケーションのデプロイ アーキテクチャは何にする必要がありますか? アプリケーションの仕様と開発コンテキストは、コラボレーションするマイクロサービスとコンテナーの形式で自律したサブシステムに分割してアプリケーションのアーキテクチャを設計することを強く提案します。これにより、マイクロサービスはそれぞれがコンテナー内で運用されます。

この方法では、各サービス (コンテナー) は、一連のまとまりのある狭い関連関数を実装します。 たとえば、アプリケーションは、カタログ サービス、注文サービス、バスケット サービス、ユーザー プロファイル サービスなどのサービスで構成される場合があります。

マイクロサービスは、HTTP (REST) などのプロトコルを使用して通信しますが、可能な限り非同期 (AMQP の使用など) も行います(特に、統合イベントを使用して更新を伝達する場合)。

マイクロサービスは、互いに独立してコンテナーとして開発およびデプロイされます。 このアプローチは、開発チームが他のサブシステムに影響を与えることなく、特定のマイクロサービスを開発およびデプロイできることを意味します。

各マイクロサービスには独自のデータベースがあり、他のマイクロサービスから完全に分離できます。 必要に応じて、コマンド とクエリの責任分離 (CQRS) で処理されるアプリケーション レベルの統合イベント (論理イベント バスを使用) を使用して、異なるマイクロサービスのデータベース間の整合性を実現します。 そのため、ビジネス上の制約では、複数のマイクロサービスと関連データベースの間の最終的な整合性を採用する必要があります。

eShopOnContainers: コンテナーを使用してデプロイされた .NET およびマイクロサービスの参照アプリケーション

知らない可能性のある架空のビジネス ドメインについて考えるのではなく、アーキテクチャとテクノロジに集中できるように、既知のビジネス ドメイン (つまり、製品のカタログを提示し、顧客からの注文を受け取り、在庫を検証し、その他のビジネス機能を実行する簡略化された e コマース (e ショップ) アプリケーション) を選択しました。 このコンテナー ベースのアプリケーション ソース コードは、 eShopOnContainers GitHub リポジトリで入手できます。

アプリケーションは、複数のストア UI フロントエンド (Web アプリケーションとネイティブ モバイル アプリ) を含む複数のサブシステムと、内部マイクロサービスへの統合エントリ ポイントとして複数の API ゲートウェイを使用して、必要なすべてのサーバー側操作用のバックエンド マイクロサービスとコンテナーで構成されます。 図 6-1 は、参照アプリケーションのアーキテクチャを示しています。

単一の Docker ホストで eShopOnContainers を使用するクライアント アプリの図。

図 6-1 開発環境用の eShopOnContainers 参照アプリケーション アーキテクチャ

上の図は、Mobile クライアントと SPA クライアントが単一の API ゲートウェイ エンドポイントと通信し、マイクロサービスと通信することを示しています。 従来の Web クライアントは、API ゲートウェイを介してマイクロサービスと通信する MVC マイクロサービスと通信します。

ホスティング環境。 図 6-1 では、1 つの Docker ホスト内に複数のコンテナーがデプロイされていることがわかります。 これは、docker-compose up コマンドを使用して単一の Docker ホストにデプロイする場合です。 ただし、オーケストレーターまたはコンテナー クラスターを使用している場合は、前のアーキテクチャ セクションで説明したように、各コンテナーが異なるホスト (ノード) で実行されている可能性があり、任意のノードで任意の数のコンテナーを実行できます。

通信アーキテクチャ。 eShopOnContainers アプリケーションでは、機能アクションの種類 (クエリと更新とトランザクション) に応じて、次の 2 種類の通信が使用されます。

  • API ゲートウェイを介した Http クライアントからマイクロサービスへの通信。 この方法は、クエリや、クライアント アプリから更新コマンドまたはトランザクション コマンドを受け入れるときに使用されます。 API ゲートウェイを使用する方法については、後のセクションで詳しく説明します。

  • 非同期イベント ベースの通信。 この通信は、マイクロサービス間で更新プログラムを伝達したり、外部アプリケーションと統合したりするために、イベント バスを介して行われます。 イベント バスは、 RabbitMQ などの任意のメッセージング ブローカー インフラストラクチャ テクノロジを使用するか、 Azure Service BusNServiceBusMassTransitBrighter などの上位 (抽象化レベル) のサービス バスを使用して実装できます。

アプリケーションは、コンテナーの形式でマイクロサービスのセットとしてデプロイされます。 クライアント アプリは、API ゲートウェイによって発行されたパブリック URL を介して、コンテナーとして実行されているマイクロサービスと通信できます。

マイクロサービスあたりのデータ主権

サンプル アプリケーションでは、すべての SQL Server データベースが 1 つのコンテナーとしてデプロイされますが、各マイクロサービスは独自のデータベースまたはデータ ソースを所有します。 この設計上の決定は、開発者が GitHub からコードを取得し、複製し、Visual Studio または Visual Studio Code で開くことを容易にするためだけです。 または、.NET CLI と Docker CLI を使用してカスタム Docker イメージをコンパイルし、Docker 開発環境でデプロイして実行することが簡単になります。 どちらの方法でも、データ ソースにコンテナーを使用すると、開発者は、インフラストラクチャ (クラウドまたはオンプレミス) にハード依存関係がある外部データベースやその他のデータ ソースをプロビジョニングすることなく、数分でビルドおよびデプロイできます。

実際の運用環境では、高可用性とスケーラビリティのために、データベースはクラウドまたはオンプレミスのデータベース サーバーに基づいている必要がありますが、コンテナーには基づいていません。

そのため、マイクロサービス (およびこのアプリケーション内のデータベースの場合も) のデプロイ単位は Docker コンテナーであり、参照アプリケーションはマイクロサービスの原則を受け入れるマルチコンテナー アプリケーションです。

その他のリソース

マイクロサービス ベースのソリューションの利点

このようなマイクロサービス ベースのソリューションには、多くの利点があります。

各マイクロサービスは比較的小さく、管理と進化が容易です。 具体的には:

  • 開発者は、優れた生産性で簡単に理解し、すぐに開始できます。

  • コンテナーはすぐに起動するため、開発者の生産性を高めます。

  • Visual Studio のような IDE では、小規模なプロジェクトを高速に読み込むことができるため、開発者の生産性が向上します。

  • 各マイクロサービスは、他のマイクロサービスとは別に設計、開発、デプロイできます。これは、新しいバージョンのマイクロサービスを頻繁にデプロイする方が簡単であるため、機敏性を提供します。

アプリケーションの個々の領域をスケールアウトすることができます。 たとえば、カタログ サービスまたはバスケット サービスはスケールアウトする必要がありますが、注文プロセスは必要ありません。 マイクロサービス インフラストラクチャは、モノリシック アーキテクチャよりもスケールアウト時に使用されるリソースに関してはるかに効率的になります。

複数のチーム間で開発作業を分割できます。 各サービスは、1 つの開発チームが所有できます。 各チームは、残りのチームとは別に、サービスを管理、開発、デプロイ、およびスケーリングできます。

問題を分離できます。 1 つのサービスに問題がある場合は、そのサービスのみが最初に影響を受けます (間違った設計が使用されている場合を除き、マイクロサービス間の直接の依存関係がある場合を除く)、他のサービスは引き続き要求を処理できます。 これに対し、モノリシック デプロイ アーキテクチャの 1 つの誤動作コンポーネントは、特にメモリ リークなどのリソースが必要な場合に、システム全体をダウンさせる可能性があります。 さらに、マイクロサービスの問題が解決された場合は、影響を受けるマイクロサービスのみをアプリケーションの残りの部分に影響を与えることなくデプロイできます。

最新のテクノロジを使用できます。 サービスの開発を独立して開始し、(コンテナーと .NET のおかげで) サイド バイ サイドで実行できるため、アプリケーション全体の古いスタックやフレームワークにスタックするのではなく、最新のテクノロジとフレームワークを効率的に使用し始めることができます。

マイクロサービス ベースのソリューションの欠点

このようなマイクロサービス ベースのソリューションには、いくつかの欠点もあります。

分散アプリケーション。 アプリケーションを配布すると、開発者がサービスを設計および構築するときに複雑さが増します。 たとえば、開発者は HTTP や AMQP などのプロトコルを使用してサービス間通信を実装する必要があります。これにより、テストと例外処理の複雑さが増します。 また、システムに待機時間が追加されます。

デプロイの複雑さ。 多数のマイクロサービスの種類があり、高いスケーラビリティが必要なアプリケーション (サービスごとに多数のインスタンスを作成し、それらのサービスを多数のホスト間で分散できる必要があります) は、IT 運用と管理のデプロイの複雑さが高いということです。 マイクロサービス指向のインフラストラクチャ (オーケストレーターやスケジューラなど) を使用していない場合、追加の複雑さがビジネス アプリケーション自体よりもはるかに多くの開発作業を必要とする可能性があります。

アトミック トランザクション。 通常、複数のマイクロサービス間のアトミック トランザクションは不可能です。 ビジネス要件では、複数のマイクロサービス間の最終的な整合性を採用する必要があります。 詳細については、べき等性メッセージ処理における課題を参照してください。

グローバル リソースのニーズの増加 (すべてのサーバーまたはホストの合計メモリ、ドライブ、およびネットワーク リソース)。 多くの場合、モノリシック アプリケーションをマイクロサービス アプローチに置き換えると、新しいマイクロサービス ベースのアプリケーションに必要な初期グローバル リソースの量は、元のモノリシック アプリケーションのインフラストラクチャ ニーズよりも大きくなります。 この方法は、粒度が高く分散サービスの場合、より多くのグローバル リソースが必要になるためです。 ただし、一般的なリソースのコストが低く、モノリシック アプリケーションを進化させる際の長期的なコストと比較してアプリケーションの特定の領域をスケールアウトできるという利点を考えると、リソースの使用の増加は、通常、大規模で長期的なアプリケーションにとって良いトレードオフとなります。

クライアントからマイクロサービスへの直接通信に関する問題。 アプリケーションが大規模で、数十個のマイクロサービスがある場合、アプリケーションでクライアントからマイクロサービスへの直接通信が必要な場合は、課題と制限があります。 1 つの問題は、クライアントのニーズと、各マイクロサービスによって公開される API の間の潜在的な不一致です。 場合によっては、クライアント アプリケーションが UI を構成するために多数の個別の要求を行う必要がある場合があります。これは、インターネット経由では非効率的であり、モバイル ネットワークでは実用的ではない可能性があります。 そのため、クライアント アプリケーションからバックエンド システムへの要求を最小限に抑える必要があります。

クライアントからマイクロサービスへの直接通信に関するもう 1 つの問題は、一部のマイクロサービスが Web に対応していないプロトコルを使用している可能性があるということです。 1 つのサービスでバイナリ プロトコルを使用する場合もあれば、別のサービスで AMQP メッセージングを使用する場合もあります。 これらのプロトコルはファイアウォールに優しくなく、内部的に最適に使用されます。 通常、アプリケーションでは、ファイアウォールの外部の通信に HTTP や WebSocket などのプロトコルを使用する必要があります。

さらに、この直接のクライアントからサービスへのアプローチのもう 1 つの欠点は、これらのマイクロサービスのコントラクトをリファクタリングすることが困難になるということです。 時間の経過と同時に、開発者はシステムをサービスに分割する方法を変更することが必要になる場合があります。 たとえば、2 つのサービスをマージしたり、1 つのサービスを 2 つ以上のサービスに分割したりできます。 ただし、クライアントがサービスと直接通信する場合、この種のリファクタリングを実行すると、クライアント アプリとの互換性が損なわれます。

アーキテクチャ セクションで説明したように、マイクロサービスに基づいて複雑なアプリケーションを設計および構築する場合は、クライアントからマイクロサービスへの直接的な通信アプローチではなく、複数のきめ細かい API ゲートウェイを使用することを検討できます。

マイクロサービスの分割。 最後に、マイクロサービス アーキテクチャに対して実行するアプローチに関係なく、もう 1 つの課題は、エンド ツー エンドアプリケーションを複数のマイクロサービスにパーティション分割する方法を決定することです。 ガイドのアーキテクチャ セクションで説明したように、いくつかの手法とアプローチを使用できます。 基本的には、他の領域から切り離され、ハード依存関係の数が少ないアプリケーションの領域を識別する必要があります。 多くの場合、このアプローチは、ユース ケース別のサービスのパーティション分割に合わせて調整されます。 たとえば、e ショップ アプリケーションでは、注文プロセスに関連するすべてのビジネス ロジックを担当する注文サービスがあります。 カタログ サービスと、他の機能を実装するバスケット サービスもあります。 理想的には、各サービスに必要な責任のセットはごくわずかです。 このアプローチは、クラスに適用される単一責任原則 (SRP) に似ています。つまり、クラスに変更する理由は 1 つだけである必要があります。 ただし、この場合はマイクロサービスに関するため、スコープは 1 つのクラスよりも大きくなります。 何より、マイクロサービスは、独自のデータ ソースに対する責任を含め、自律的でエンドツーエンドである必要があります。

外部アーキテクチャと内部アーキテクチャと設計パターン

外部アーキテクチャは、このガイドのアーキテクチャ セクションで説明されている原則に従って、複数のサービスで構成されるマイクロサービス アーキテクチャです。 ただし、各マイクロサービスの性質に応じて、および選択した高度なマイクロサービス アーキテクチャとは無関係に、マイクロサービスごとに異なるパターンに基づいて異なる内部アーキテクチャを使用することが一般的であり、場合によっては推奨されます。 マイクロサービスでは、さまざまなテクノロジとプログラミング言語を使用することもできます。 図 6-2 は、この多様性を示しています。

外部アーキテクチャ パターンと内部アーキテクチャ パターンを比較した図。

図 6-2 外部アーキテクチャと内部アーキテクチャと設計

たとえば、 eShopOnContainers サンプルでは、カタログ、バスケット、およびユーザー プロファイルマイクロサービスは単純です (基本的には CRUD サブシステム)。 そのため、内部アーキテクチャと設計は簡単です。 ただし、注文マイクロサービスなど、その他のマイクロサービスは、より複雑で、ドメインの複雑さの度合いが高い絶えず変化するビジネス ルールを表している場合があります。 このような場合は、 eShopOnContainers の注文マイクロサービスで行っているように、ドメイン駆動設計 (DDD) アプローチで定義されたパターンのように、特定のマイクロサービス内でより高度なパターンを実装することをお勧めします。 (これらの DDD パターンについては、後のセクションで、マイクロサービスを注文する eShopOnContainers の実装について説明します)。

マイクロサービスごとに異なるテクノロジのもう 1 つの理由は、各マイクロサービスの性質です。 たとえば、C# のようなオブジェクト指向のプログラミング言語ではなく、F# などの関数型プログラミング言語を使用する方が良い場合もあれば、AI や機械学習ドメインを対象としている場合は R のような言語を使用することをお勧めします。

要するに、各マイクロサービスは、異なる設計パターンに基づいて異なる内部アーキテクチャを持つことができます。 高度な DDD パターンを使用してすべてのマイクロサービスを実装する必要があるわけではありません。これは、マイクロサービスを過剰にエンジニアリングするためです。 同様に、絶えず変化するビジネス ロジックを持つ複雑なマイクロサービスは、CRUD コンポーネントとして実装しないでください。または、最終的に低品質のコードになる可能性があります。

新しい世界: 複数のアーキテクチャ パターンとポリグロット マイクロサービス

ソフトウェア アーキテクトや開発者が使用するアーキテクチャ パターンは多数あります。 アーキテクチャ のスタイルとアーキテクチャ パターンを混在させ、いくつかのパターンを次に示します。

また、ASP.NET Core Web API、NancyFx、ASP.NET Core SignalR (.NET Core 2 以降で利用可能)、F#、Node.js、Python、Java、C++、GoLang など、多くのテクノロジと言語を使用してマイクロサービスを構築することもできます。

重要な点は、特定のアーキテクチャ パターンやスタイル、または特定のテクノロジが、すべての状況に適していないということです。 図 6-3 は、さまざまなマイクロサービスで使用できる一部のアプローチとテクノロジ (ただし、特定の順序ではない) を示しています。

ポリグロットの世界アーキテクチャにおける 12 個の複雑なマイクロサービスを示す図。

図 6-3 マルチアーキテクチャ パターンとポリグロット マイクロサービスの世界

マルチアーキテクチャ パターンとポリグロット マイクロサービスは、言語とテクノロジを組み合わせて各マイクロサービスのニーズに合わせ、互いに通信できることを意味します。 図 6-3 に示すように、多くのマイクロサービスで構成されるアプリケーション (ドメイン駆動型設計用語の境界付きコンテキスト、または単に自律マイクロサービスとしての "サブシステム") では、各マイクロサービスを異なる方法で実装できます。 アーキテクチャ パターンが異なる場合があり、アプリケーションの性質、ビジネス要件、優先順位に応じて異なる言語とデータベースが使用される場合があります。 場合によっては、マイクロサービスが似ている場合があります。 ただし、各サブシステムのコンテキスト境界と要件は通常異なるため、通常はそうではありません。

たとえば、単純な CRUD メンテナンス アプリケーションの場合、DDD パターンを設計して実装するのは意味がない場合があります。 ただし、コア ドメインまたはコア ビジネスでは、絶えず変化するビジネス ルールを使用してビジネスの複雑さに対処するために、より高度なパターンを適用することが必要になる場合があります。

特に、複数のサブシステムで構成される大規模なアプリケーションを処理する場合は、1 つのアーキテクチャ パターンに基づいて 1 つの最上位アーキテクチャを適用しないでください。 たとえば、CQRS はアプリケーション全体の最上位アーキテクチャとして適用しないでくださいが、特定のサービス セットに役立つ場合があります。

特定のケースに万能な解決策や正しいアーキテクチャのパターンは存在しません。 "1 つのアーキテクチャ パターンですべてをルール化する" ことはできません。次のセクションで説明するように、各マイクロサービスの優先順位に応じて、それぞれ異なるアプローチを選択する必要があります。