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

ヒント

このコンテンツは eBook の「コンテナー化された .NET アプリケーションの .NET マイクロサービス アーキテクチャ」からの抜粋です。.NET Docs で閲覧できるほか、PDF として無料ダウンロードすると、オンラインで閲覧できます。

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

このセクションでは、仮想のサーバー側エンタープライズ アプリケーションの開発について説明します。

アプリケーションの仕様

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

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

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

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

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

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

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

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

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

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

  • さまざまなビジネス領域のアプリケーションにフォーカスしている複数の開発チームがあること。

  • 新しいチーム メンバーは短時間で生産的になる必要があり、アプリケーションを簡単に理解して変更できる必要があること。

  • アプリケーションは長期間進化し、ビジネス ルールは常に変化すること。

  • 適切な長期にわたる保守容易性が必要なこと。これは、将来新しい変更を機敏に実装する一方で、複数のサブシステムを他のサブシステムに与える影響を最小限に抑えながら更新できることを意味します。

  • アプリケーションの継続的インテグレーションと継続的デプロイを実践すること。

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

アーキテクチャの選択

アプリケーションを配置するアーキテクチャは何にすべきでしょうか。 アプリケーションの仕様と開発コンテキストでは、アプリケーションを協調して動作するマイクロサービスとコンテナーの形の自律的なサブシステムに分解して設計することを強くお勧めします。ここでは、マイクロサービスはコンテナーになります。

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

マイクロサービスは、HTTP (REST) などのプロトコルを使用して通信しますが、可能であれば常に非同期 (AMQP の使用など) でも通信します。これは、特に統合イベントの更新を伝達するときに実行されます。

マイクロサービスは、互いに独立したコンテナーとして開発と配置が行われます。 この方法は、開発チームが特定のマイクロサービスを、他のサブシステムに影響を与えずに開発してデプロイできることを意味します。

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

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

知識を持っていない可能性がある仮想ビジネス ドメインを検討する代わりに、アーキテクチャとテクノロジにフォーカスできるようにするために、既知のビジネス ドメインが選択されています。具体的には、製品カタログの提示、顧客からの注文の受け取り、在庫の確認、およびその他のビジネス機能を実行する単純化された eコマース (eショップ) アプリケーションです。 このコンテナー ベースのアプリケーションのソース コードは、eShopOnContainers GitHub リポジトリから入手できます。

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

Diagram of client apps using eShopOnContainers in a single Docker host.

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

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

ホスト環境。 図 6-1 で、単一の Docker ホスト内に配置されたさまざまなコンテナーを確認します。 これは、docker-compose up コマンドを使用して単一の Docker ホストに配置する場合のものです。 ただし、オーケストレーターまたはコンテナー クラスターを使用する場合は、各コンテナーを別のホスト (ノード) で実行でき、任意のノードで任意の数のコンテナーを実行できます。これについては、アーキテクチャのセクションで既に説明しました。

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

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

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

アプリケーションは、コンテナーの形の一連のマイクロサービスとして配置されます。 クライアント アプリは、API ゲートウェイによって公開されたパブリック URL 経由でコンテナーとして実行されているこれらのマイクロサービスと通信できます。

マイクロサービス単位のデータ管理

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

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

そのため、マイクロサービス (およびこのアプリケーション内のデータベース) の配置の単位は Docker コンテナーであり、リファレンス アプリケーションはマイクロサービスの 原則を採用するマルチコンテナー アプリケーションです。

その他の技術情報

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

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

各マイクロサービスが比較的小さい - 管理しやすく進化させやすい。 具体的には、次のように使用します。

  • 開発者が理解しやすく、適切な生産性ですぐに開始できます。

  • コンテナーは短時間で開始されます。これにより、開発者の生産性が向上します。

  • Visual Studio のような IDE は、小さなプロジェクトを高速で読み込みます。これにより、開発者の生産性が向上します。

  • 各マイクロサービスの設計、開発、およびデプロイは、他のマイクロサービスとは無関係に実行できます。これにより、新しいバージョンのマイクロサービスを頻繁にデプロイするのが容易になるため、機敏性が提供されます。

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

開発作業を複数のチームに分割できます。 各サービスは、1 つの開発チームが所有できます。 各チームは、管理、開発、配置、およびスケーリングを、残りのチームとは無関係に実行できます。

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

最新のテクノロジを利用できます。 サービスの開発を個別に開始し、同時に実行できるため (コンテナーと .NET のおかげです)、アプリケーション全体で古いスタックやフレームワークにとらわれることなく最新のテクノロジとフレームワークの使用を臨機応変に開始できます。

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

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

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

配置の複雑性。 数十種類のマイクロサービスがあり、高いスケーラビリティを必要とするアプリケーション (サービスごとに複数のインスタンスを作成でき、それらのサービスを複数のホスト間でバランスを取る必要があります) では、IT の運用と管理に対する配置の複雑さが高まります。 マイクロサービス指向のインフラストラクチャ (オーケストレーターとスケジューラなど) を使用していない場合、複雑さの上昇によって、ビジネス アプリケーション自体を大きく上回る開発努力が必要になる可能性があります。

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

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

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

クライアントからマイクロサービスへの直接的な通信に伴う別の問題は、一部のマイクロサービスで使用するプロトコルが Web フレンドリではない場合があることです。 あるサービスではバイナリ プロトコルが使用され、別のサービスでは AMQP メッセージングが使用されることがあります。 これらのプロトコルはファイアウォール フレンドリではなく、最善なのは内部的に使用することです。 通常、アプリケーションは、ファイアウォールの外部と通信するために HTTP や Websocket などのプロトコルを使用する必要があります。

さらに、クライアントからマイクロサービスへの直接的な通信に伴う別のマイナス面は、これらのマイクロサービスのコントラクトのリファクタリングが困難になることです。 時間の経過と共に、開発者は、システムをサービスに分割する方法を変更する可能性があります。 たとえば、2 つのサービスをマージしたり、1 つのサービスを複数のサービスに分割したりします。 ただし、クライアントがサービスと直接的に通信している場合、この種のリファクタリングを実行すると、クライアント アプリとの互換性が破られる可能性があります。

アーキテクチャのセクションで説明したように、マイクロサービスに基づく複雑なアプリケーションの設計とビルドを行うときは、単純なクライアントからマイクロサービスへの直接的な通信ではなく、複数の粒度の細かい API ゲートウェイの使用を検討してください。

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

外部アーキテクチャ、内部アーキテクチャ、および設計パターン

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

Diagram comparing external and internal architecture patterns.

図 6-2。 外部アーキテクチャ、内部アーキテクチャ、および設計

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

マイクロサービスごとに異なるテクノロジを使用する別の理由は、各マイクロサービスの性質です。 たとえば、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 は、さまざまなマイクロサービスで使用できる可能性があるいくつかのアプローチとテクノロジを示しています (順不同です)。

Diagram showing 12 complex microservices in a polyglot world architecture.

図 6-3。 複数のアーキテクチャ パターンと多言語マイクロサービスの世界

複数のアーキテクチャ パターンと多言語マイクロサービスとは、言語とテクノロジを混合して、各マイクロサービスのニーズに一致させることができ、引き続き互いに通信できるということです。 図 6-3 に示すように、多数のマイクロサービス (ドメイン駆動設計の用語では境界コンテキスト、自律型のマイクロサービスでは単に "サブシステム" と呼ばれます) で構成されるアプリケーションでは、各マイクロサービスを異なる方法で実装できます。 それぞれのアーキテクチャ パターンは異なる場合があり、アプリケーションの性質、ビジネス要件、および優先度に応じて異なる言語とデータベースを使用できます。 場合によっては、複数のマイクロサービスが同じようになることがあります。 各サブシステムのコンテキスト境界と要件は通常異なっているため、通常はそのような状態にはなりません。

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

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

すべてのケースに対応できる特効薬のようなアーキテクチャ パターンは存在しません。 "すべてを制御する 1 つのアーキテクチャ パターン" を持つことはできません。各マイクロサービスの優先度に基づき、次のセクションで説明するように、それぞれに異なる手法を選択する必要があります。