腐敗防止層パターン - AWS 規範ガイダンス

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

腐敗防止層パターン

Intent

腐敗防止層 (ACL) パターンは、ドメインモデルのセマンティクスをあるシステムから別のシステムに変換する仲介レイヤーとして機能します。これにより、確立されている通信規約がアップストリームチームによって消費される前に、アップストリームの境界コンテキスト (モノリス) のモデルをダウンストリームの境界コンテキスト (マイクロサービス) に適したモデルに変換します。このパターンは、ダウンストリームの境界コンテキストにコアサブドメインが含まれている場合、またはアップストリームモデルが変更不可能なレガシーシステムである場合に適用できます。また、呼び出しをターゲットシステムに透過的にリダイレクトする必要がある場合に呼び出し元が変更されないようにし、変革上のリスクやビジネスの中断を軽減します。

導入する理由

移行プロセスにあるモノリシックアプリケーションをマイクロサービスに移行すると、新しく移行したサービスのドメインモデルセマンティクスが変更される可能性があります。これらのマイクロサービスを呼び出すためにモノリス内の機能が必要な場合は、呼び出し元のサービスを変更せずに、呼び出しを移行済みサービスにルーティングする必要があります。ACL パターンを使用すると、モノリス側では、このパターンが呼び出しを新しいセマンティクスに変換するアダプターまたはファサードレイヤーとして機能し、マイクロサービスを透過的に呼び出すことができます。

適用対象

次の場合は、このパターンの使用を検討してください。

  • 既存のモノリシックアプリケーションがマイクロサービスに移行済みの関数と通信する必要があり、移行済みサービスドメインモデルとセマンティクスの機能が元の機能とは異なっている。

  • 2 つのシステムのセマンティクスが異なっている状態で、データ交換が必要になっているが、双方のシステムで互換性を確保するのは実用的でない。

  • 迅速かつシンプルなアプローチを取ることで、影響を最小限に抑えながら、あるシステムを別のシステムに適応させなければならない。

  • 対象のアプリケーションが外部システムと通信している。

問題点と考慮事項

  • チームの依存関係: システム内の各種サービスを異なるチームが所有していると、移行済みサービスの新しいドメインモデルセマンティクスが理由で、呼び出し元システムに変更が必要になるかもしれません。しかし、他に優先すべき事項があり、調整しながらはそうした変更を行えない場合があります。ACL を使用すると、呼び出し先を切り離し、呼び出しが新しいサービスのセマンティクスと一致するように変換されるため、呼び出し側で現在のシステムを変更しなくても済みます。

  • 運用上のオーバーヘッド: ACL パターンの使用には、運用および保守にさらに労力が必要となります。例えば、ACL を、モニタリングおよびアラートツール、リリースプロセス、継続的インテグレーションおよび継続的デリバリー (CI/CD) プロセスなどと統合する作業も発生するからです。

  • 単一障害点: ACL で障害が発生すると、ターゲットサービスへの到達が不可能になり、アプリケーションの問題が発生する可能性があります。この問題を軽減するには、再試行機能とサーキットブレーカーを構築する必要があります。こうしたオプションの詳細については、バックオフでの再試行サーキットブレーカーの各パターンを参照してください。適切なアラートとログ記録を設定すると、平均解決時間 (MTTR) が向上します。

  • 技術的負債: 移行またはモダナイズ戦略の一環として、ACL が一時的または暫定的なソリューションとなるか、長期的なソリューションとなるかを検討してください。暫定的なものである場合は、ACL を技術的負債として記録し、それに依存する呼び出し元の移行が完了した後に、その ACL を廃止する必要があります。

  • レイテンシー: レイヤーを追加すると、あるインターフェイスから別のインターフェイスにリクエストが変換されることで、レイテンシーが発生する可能性があります。本番環境に ACL をデプロイする場合は、その前に、応答時間の影響を受けやすいアプリケーションでパフォーマンス耐性を定義し、テストすると良いでしょう。

  • スケーリング上のボトルネック: ピーク負荷までサービスがスケール可能な高負荷アプリケーションでは、ACL がボトルネックになり、スケーリング上の問題が発生する可能性があります。ターゲットサービスがオンデマンドでスケールする場合は、それに応じてスケールが可能となるように ACL を設計する必要があります。

  • サービス固有の実装または共有実装: ACL を共有オブジェクトとして設計すると、呼び出しを複数のサービスやサービス固有のクラスに変換およびリダイレクトできます。ACL の実装タイプを決定する際には、レイテンシー、スケーリング、障害耐性を考慮してください。

実装

ACL は、移行対象サービスに固有のクラスとして、または独立したサービスとして、モノリシックアプリケーション内に実装できます。また、ACL は、それに依存するサービスをマイクロサービスアーキテクチャにすべて移行した後に廃止する必要があります。

高レベルのアーキテクチャ

次のアーキテクチャ例に示すモノリシックアプリケーションには、ユーザーサービス、カートサービス、アカウントサービスという 3 つのサービスがあります。カートサービスはユーザーサービスに依存しており、アプリケーションではモノリシックリレーショナルデータベースが使用されています。

3 つのサービスがあるモノリシックアプリケーション。

次のアーキテクチャでは、ユーザーサービスが新しいマイクロサービスに移行済みです。カートサービスはユーザーサービスを呼び出しますが、対象サービスはモノリス内で利用できなくなっています。 また、新しく移行したサービスのインターフェイスが、モノリシックアプリケーション内にあった旧サービスのインターフェイスと一致しない可能性もあります。

1 つのサービスがマイクロサービスに移動済みのモノリシックアプリケーション。

カートサービスが新しく移行したユーザーサービスを直接呼び出す必要がある場合は、カートサービスを変更し、モノリシックアプリケーションを徹底的にテストしなければなりません。これにより、変革上のリスクやビジネスの中断が増える可能性があります。そのため、モノリシックアプリケーションが既に持つ機能への変更を最小限に抑えることを目指すべきです。

この場合、古いユーザーサービスと新しく移行したユーザーサービスの間に ACL を導入すると良いでしょう。ACL は、呼び出しを新しいインターフェイスに変換するアダプターまたはファサードとして機能し、移行済みサービスに固有のクラス (UserServiceFacadeUserServiceAdapter など) としてモノリシックアプリケーション内に実装できます。また、腐敗防止層は、それに依存するサービスをマイクロサービスアーキテクチャにすべて移行した後に廃止する必要があります。

腐敗防止層を追加します。

AWS のサービスを使用した実装

次の図は、AWS サービスを使用してこの ACL の例を実装する方法を示しています。

AWS サービスを使用して ACL パターンを実装します。

ユーザーマイクロサービスは、ASP.NET モノリシックアプリケーションから移行し、AWS 上に AWS Lambda 関数としてデプロイします。Lambda 関数への呼び出しは Amazon API Gateway 経由でルーティングします。モノリス内に ACL をデプロイすることで、呼び出しを変換し、ユーザーマイクロサービスのセマンティクスに適合させます。

Program.cs がモノリス内のユーザーサービス (UserInMonolith.cs) を呼び出すと、呼び出しは、ACL (UserServiceACL.cs) にルーティングされます。次に ACL が、呼び出しを新しいセマンティクスとインターフェイスに変換し、さらに、API ゲートウェイエンドポイント経由でマイクロサービスを呼び出します。呼び出し元 (Program.cs) は、ユーザーサービスおよび ACL で実行される変換とルーティングを認識していません。呼び出し元では、コード変更が認識されないため、ビジネスの中断や変革上のリスクが軽減されます。

サンプルコード

次のコードスニペットは、元のサービスへの変更と、UserServiceACL.cs の実装を示しています。リクエストを受信すると、元のユーザーサービスは、ACL を呼び出します。ACL は、ソースオブジェクトを新しく移行したサービスのインターフェイスに合わせて変換し、その後サービスを呼び出して、呼び出し元にレスポンスを返します。

public class UserInMonolith: IUserInMonolith { private readonly IACL _userServiceACL; public UserInMonolith(IACL userServiceACL) => (_userServiceACL) = (userServiceACL); public async Task<HttpStatusCode> UpdateAddress(UserDetails userDetails) { //Wrap the original object in the derived class var destUserDetails = new UserDetailsWrapped("user", userDetails); //Logic for updating address has been moved to a microservice return await _userServiceACL.CallMicroservice(destUserDetails); } } public class UserServiceACL: IACL { static HttpClient _client = new HttpClient(); private static string _apiGatewayDev = string.Empty; public UserServiceACL() { IConfiguration config = new ConfigurationBuilder().AddJsonFile(AppContext.BaseDirectory + "../../../config.json").Build(); _apiGatewayDev = config["APIGatewayURL:Dev"]; _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); } public async Task<HttpStatusCode> CallMicroservice(ISourceObject details) { _apiGatewayDev += "/" + details.ServiceName; Console.WriteLine(_apiGatewayDev); var userDetails = details as UserDetails; var userMicroserviceModel = new UserMicroserviceModel(); userMicroserviceModel.UserId = userDetails.UserId; userMicroserviceModel.Address = userDetails.AddressLine1 + ", " + userDetails.AddressLine2; userMicroserviceModel.City = userDetails.City; userMicroserviceModel.State = userDetails.State; userMicroserviceModel.Country = userDetails.Country; if (Int32.TryParse(userDetails.ZipCode, out int zipCode)) { userMicroserviceModel.ZipCode = zipCode; Console.WriteLine("Updated zip code"); } else { Console.WriteLine("String could not be parsed."); return HttpStatusCode.BadRequest; } var jsonString = JsonSerializer.Serialize<UserMicroserviceModel>(userMicroserviceModel); var payload = JsonSerializer.Serialize(userMicroserviceModel); var content = new StringContent(payload, Encoding.UTF8, "application/json"); var response = await _client.PostAsync(_apiGatewayDev, content); return response.StatusCode; } }

GitHub リポジトリ

このパターンのサンプルアーキテクチャの完全な実装については、https://github.com/aws-samples/anti-corruption-layer-pattern にある GitHub リポジトリを参照してください。

関連情報