本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
防腐层模式
意图
防腐层(ACL)模式充当中介层,将域模型语义从一个系统转换为另一个系统。在使用上游团队建立的通信合同之前,它将上游限界上下文(单体)的模型转换为适合下游限界上下文(微服务)的模型。当下游限界上下文包含核心子域,或者上游模型是不可修改的遗留系统时,这种模式可能适用。它还能降低转型风险和业务中断,方法是防止在调用方必须透明地将调用重定向到目标系统时对其进行更改。
动机
在迁移过程中,当单体应用程序迁移到微服务时,新迁移的服务的域模型语义可能会发生更改。当需要使用单体架构中的功能来调用这些微服务时,应将调用路由到迁移的服务,而无需对调用服务进行任何更改。ACL 模式允许单体通过充当适配器或将调用转换为更新的语义的 Facade 层来透明地调用微服务。
适用性
在以下情况下,考虑使用此模式:
-
您现有的单体应用程序必须与已迁移到微服务的功能进行通信,并且迁移的服务域模型和语义与原始功能不同。
-
两个系统具有不同的语义,且需要交换数据,但修改一个系统使其与另一个系统兼容是不切实际的。
-
您想使用一种快速简化的方法来使一个系统适应另一个系统,同时将影响降至最低。
-
您的应用程序正在与外部系统通信。
问题和注意事项
-
团队依赖关系:当系统中的不同服务由不同的团队拥有时,迁移服务中的新域模型语义可能会导致调用系统发生变化。但是,团队可能无法以协调的方式进行这些更改,因为他们可能还有其他优先事项。ACL 解耦被调用方并转换调用以匹配新服务的语义,从而无需调用方在当前系统中进行更改。
-
运营开销:ACL 模式需要额外的工作来操作和维护。此工作包括将 ACL 与监控和提醒工具、发布流程以及持续集成和持续交付(CI/CD)流程集成。
-
单点故障:ACL 中的任何故障都可能使目标服务无法访问,从而导致应用程序问题。为了缓解此问题,您应该内置重试功能和断路器。请参阅使用回退重试和断路器模式,以了解有关这些选项的更多信息。设置适当的提醒和日志记录将缩短平均解决时间(MTTR)。
-
技术债务:作为迁移或现代化战略的一部分,请考虑 ACL 是暂时或临时解决方案,还是长期解决方案。如果是临时解决方案,您应将 ACL 记录为技术债务,并在所有依赖调用方迁移完毕后将其停用。
-
延迟:由于请求需要从一个接口转换到另一个接口,额外层可能会带来延迟。我们建议您在将 ACL 部署到生产环境之前,先定义和测试对响应时间敏感的应用程序的性能容错能力。
-
扩展瓶颈:在服务可以扩展到峰值负载的高负载应用程序中,ACL 可能会成为瓶颈,并可能导致扩展问题。如果目标服务按需扩展,您应设计 ACL 以相应地进行扩展。
-
特定于服务或共享的实现:您可以将 ACL 设计为共享对象,以将调用转换和重定向到多个服务或特定于服务的类别。在确定 ACL 的实现类型时,请考虑延迟、扩展和容错能力。
实现
您可以在单体应用程序中将 ACL 作为特定于要迁移的服务的类别或独立的服务来实现。在所有从属服务都迁移到微服务架构后,必须停用 ACL。
高级架构
在以下示例架构中,单体应用程序具有三项服务:用户服务、购物车服务和账户服务。购物车服务依赖于用户服务,应用程序使用单体关系数据库。
在以下架构中,用户服务已迁移到新的微服务。购物车服务调用用户服务,但在单体中不再提供该实现。 新迁移的服务的接口也可能与其之前(在单体应用程序中时)的接口不匹配。
如果购物车服务必须直接调用新迁移的用户服务,则需要更改购物车服务并对单体应用程序进行全面测试。这可能会增加转型风险和业务中断。目标应该是最大限度地减少对单体应用程序现有功能的更改。
在这种情况下,我们建议您在旧的用户服务和新迁移的用户服务之间引入 ACL。ACL 可用作适配器或 Facade,以将调用转换为较新的接口。ACL 可以在单体应用程序内作为特定于已迁移服务的类别(例如 UserServiceFacade 或 UserServiceAdapter)来实现。在所有从属服务都迁移到微服务架构后,必须停用防腐层。
使用 AWS 服务来实施
下图显示了如何使用 AWS 服务实现此 ACL 示例。
用户微服务从 ASP.NET 单体应用程序中迁移出来,并作为 AWS Lambda
在 Program.cs 调用单体内的用户服务(UserInMonolith.cs)时,该调用会路由到 ACL(UserServiceACL.cs)。ACL 将调用转换为新的语义和接口,并通过 API Gateway 端点调用微服务。调用方(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 存储库
有关此模式示例架构的完整实施,请参阅 GitHub 存储库 https://github.com/aws-samples/anti-corruption-layer-pattern