こんにちは、ほっそーです!
この記事は「2025 Japan AWS Jr. Champions 夏のQiitaリレー」の20日目の記事です。
過去の投稿(リンク集)は 以下からご覧ください!
はじめに
先日参加した AWS Summitで、「AWS 規範ガイダンス〜クラウド設計パターン、アーキテクチャ、および実装の解説〜(AWS-41)」というセッションが特に印象に残りました。
紹介されていた内容は、AWS公式でも参考資料が展開されていますので、気になる方は以下をご参照ください。
業務では AWSを日常的に使っていますが、クラウド設計パターンを体系的に学んだことはなく、セッションで紹介された内容も初見では理解しきれませんでした(まだまだだなと痛感…)。
ある程度業務を自走できるようになった一方で、設計力・成長力の頭打ち感を感じており、その一因として「AWS の設計思想やベストプラクティスの理解不足」があるのではと考えています。
本記事では、セッションで紹介されていた設計パターンのひとつ「腐敗防止層(Anti-Corruption Layer)」について、自分なりの解釈と気づきを整理していきます!
今後も他の設計パターンについてシリーズ形式でまとめていく予定ですので、設計に関心のある方の参考になれば嬉しいです。
腐敗防止層(ACL: Anti-Corruption Layer)とは
破損防止レイヤー(ACL)パターンは、ドメインモデルのセマンティクスをあるシステムから別のシステムに変換する仲介レイヤーとして機能します。アップストリームの境界コンテキスト(モノリス)のモデルを、アップストリームチームによって確立された通信契約を消費する前に、ダウンストリームの境界コンテキスト(マイクロサービス)に適したモデルに変換します。このパターンは、ダウンストリームの境界コンテキストにコアサブドメインが含まれている場合、またはアップストリームモデルが変更不可能なレガシーシステムである場合に適用できます。また、呼び出しをターゲットシステムに透過的にリダイレクトする必要がある場合に発信者への変更を防ぐことで、変換リスクとビジネスの中断を軽減します。
こちらは、一言で言うならば、
「モノリスの複雑な仕組みをそのまま引きずらず、マイクロサービス側の都合に合わせてくれる“仲介役”」 的なイメージです。
モノリスなシステムを運用している場合、
「マイクロサービス化したい」「一部の機能だけでも切り出したい」と考えることはあると思います。
ただ、モノリスなシステムでは、
- 依存関係の複雑さ
- データアクセスの一体化
- ビジネスロジックの分離困難さ
といった制約があるため、そう簡単には切り出せません。
たとえば、「モノリスの中にある一部機能を、Lambda+API Gateway で別サービスとして分離したい」 と考えても、既存システムとのつなぎ込みがネックになります。
「できれば呼び出し元を変更したくない」という気持ちになりますよね。
そのときに考えられるアプローチが、この 腐敗防止層パターン です。
モノリス側に ACL のようなファサードクラスを用意すれば、古いインターフェース(Cert Service)を保ちつつ、新しいマイクロサービス(User Service)と接続することが可能になります。
腐敗防止層(ACL)の実装例を解説
実装例でイメージをつかんでみましょう。
クライアントからモノリスなアプリケーションに対してリクエストを送ったとき、実は内部ではACL(Anti-Corruption Layer)を経由して、API Gateway 〜 Lambda で構成されたマイクロサービスの処理が呼び出されている…そんなケースを想定した実装例がAWS公式で紹介されていましたので、処理を追ってみます。
モノリス側,マイクロサービス呼び出しをACLに任せる
public class UserInMonolith: IUserInMonolith
{
private readonly IACL _userServiceACL;
// ACL(Anti-Corruption Layer)を依存注入
public UserInMonolith(IACL userServiceACL) => (_userServiceACL) = (userServiceACL);
public async Task<HttpStatusCode> UpdateAddress(UserDetails userDetails)
{
// モノリス側のUserDetailsを、ACL側で扱える形式(ラップクラス)に変換
var destUserDetails = new UserDetailsWrapped("user", userDetails);
// ACLを通じてマイクロサービスに更新要求を転送
return await _userServiceACL.CallMicroservice(destUserDetails);
}
}
-
- UserInMonolithはモノリスの既存クラスで、外部(マイクロサービス)の仕様を知らない
- IACLインターフェースを通じて、呼び出しの責務をACLに丸投げする
- UserDetailsWrappedによって、ACL側が期待する ISourceObjectに適合させている
ACL側,変換と通信を一手に担う仲介レイヤー
public class UserServiceACL: IACL
{
// HttpClientを静的に保持(再利用)
static HttpClient _client = new HttpClient();
// API GatewayのURL(設定ファイルから読み込む)
private static string _apiGatewayDev = string.Empty;
public UserServiceACL()
{
// config.json から API Gateway のURLを読み込む
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile(AppContext.BaseDirectory + "../../../config.json")
.Build();
_apiGatewayDev = config["APIGatewayURL:Dev"];
// JSON形式で受け入れるようにヘッダーを設定
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<HttpStatusCode> CallMicroservice(ISourceObject details)
{
// Service名を元にマイクロサービスのエンドポイントを構成
_apiGatewayDev += "/" + details.ServiceName;
Console.WriteLine(_apiGatewayDev);
// 元のISourceObjectをUserDetailsにキャスト(実行時に失敗するとnullになる可能性あり)
var userDetails = details as UserDetails;
// マイクロサービス側のDTOに変換(内部モデルを外部用にマッピング)
var userMicroserviceModel = new UserMicroserviceModel
{
UserId = userDetails.UserId,
Address = userDetails.AddressLine1 + ", " + userDetails.AddressLine2,
City = userDetails.City,
State = userDetails.State,
Country = userDetails.Country
};
// ZipCodeをint型として検証・変換
if (Int32.TryParse(userDetails.ZipCode, out int zipCode))
{
userMicroserviceModel.ZipCode = zipCode;
Console.WriteLine("Updated zip code");
}
else
{
// 変換失敗時はBadRequestを返却(サービス呼び出しせず終了)
Console.WriteLine("String could not be parsed.");
return HttpStatusCode.BadRequest;
}
// DTOをJSONにシリアライズ
var payload = JsonSerializer.Serialize(userMicroserviceModel);
// POST用のHttpContentを作成(Content-Type: application/json)
var content = new StringContent(payload, Encoding.UTF8, "application/json");
// マイクロサービスへPOSTリクエストを送信
var response = await _client.PostAsync(_apiGatewayDev, content);
// ステータスコードのみ返却(呼び出し元に返す)
return response.StatusCode;
}
}
-
- ACLの役割は「モデル変換」と「プロトコル変換」
- UserDetails → UserMicroserviceModel に変換(=セマンティクス変換)
- C#オブジェクト → JSON(=プロトコル変換)
- ACLの役割は「モデル変換」と「プロトコル変換」
フロー図にしてみるとこんな感じでしょうか。ACLを介することで設計思想の違いを吸収し、既存システムに影響を与えずに連携できる構造が見えてきました。
導入時に考慮すべきポイント
ACLパターンを知ると、「これ使えば既存システムに優しくマイクロサービス化できるやん!」と思いがちですが、当然ながら、メリットの裏にはコストやリスクも存在します。
特に以下の3点は、実際に導入する前にチーム内で議論すべきポイントだと感じました。
実装〜運用コストの増加
- モデル変換やAPI呼び出し処理など、実装負荷や保守対象が増えます
- 一時的なつなぎとして導入する場合は「いつ・どうやって外すか」、恒久的に使うなら「継続的に運用しやすい構成か」を意識しておく必要があります
単一障害点になりうる
- ACLがシステム間連携の通り道になるため、落ちると連携全体が止まってしまうリスクがあります
- サーキットブレーカーやリトライ設計、ACL自体のスケーラビリティ確保といった対策も合わせて検討したいところです
レイテンシーの増加
- モデル変換、JSONシリアライズ、ネットワーク通信などが追加されるため、
処理レイテンシがわずかに増える可能性があります - 高頻度で呼び出される場合や、変換処理が重い場合は特に注意が必要です
ACLは「つなぎ込みをラクにするレイヤー」ではあるけれど、アーキテクチャ的には1つの責任とリスクを背負います。
安易に入れるのではなく、役割と期間を明確にして設計するのが大切だと感じました。
改めてまとめると次のとおり
観点 | 内容 |
---|---|
適用が有効なケース | - モノリス → マイクロサービス移行時にインターフェースの違いがある場合 - 外部システムとの接続が必要だが、内部モデルを汚したくない場合 - 呼び出し元の改修を最小限にしたい場合 |
メリット | - モノリスとマイクロサービス間の変換レイヤーを明確にできる - 設計思想の違いによる汚染を防止できる - 呼び出し元への影響を抑えた段階的移行が可能 |
デメリット/考慮事項 | - 運用コストの増加 → 監視・CI/CD・保守の対象が増える - 技術的負債化のリスク → 恒久的か一時的かを明確にしておく - 単一障害点になりうる → 再試行やサーキットブレーカーの導入が必要 - スケーリングのボトルネック → ACL自体のスケーラビリティ確保が必要 - レイテンシーの増加 → 変換処理の遅延に注意が必要 |
おわりに
今回は、AWSクラウド設計パターン「腐敗防止層(Anti-Corruption Layer)」 について整理してみました。
腐敗という言葉の通り、異なる設計思想のシステムをそのまま直結すると、予期せぬ依存や副作用が広がり、ドメインモデルの健全性が損なわれる可能性があります。
「正しいドメインは守るべきもの」という視点は、システム全体の健全性を保つうえでも重要だと感じました。
また設計パターンは、初めて見ると抽象的で捉えづらい部分もありますが、自分の業務や過去の経験に照らし合わせながら考えることで、理解が深まると実感しました。
今後も他の設計パターンを紹介していく予定です。興味があればぜひ続編もチェックしていただけると嬉しいです!
参考・引用元
本記事内のACLパターンの説明文および図は、上記ドキュメントを参考・引用しています。