反腐败层模式
整合策略:
反腐败层:
结论
与遗留系统集成并非易事,这已不是什么秘密。糟糕的文档、缺乏支持、混乱的界面以及大量潜在的错误,只是您在集成过程中可能遇到的问题的一部分。然而,出于技术和/或政策方面的原因,集成往往是绝对必要的。与遗留系统集成会给正在设计的系统带来风险,因为遗留模型通常设计不佳,因此您精心设计的新模型很可能会被遗留系统的集成所破坏。
整合策略:
需要使用遗留系统的开发人员面临四种选择:
1- 抛弃遗留系统:拥有共同的用例并不总是集成的理由。开发人员应该仔细评估遗留系统的价值,并将其与集成成本进行权衡,以此来考虑是否进行集成。
2. 遵循遗留系统:如果组织没有更换遗留系统的计划,并且遗留模型的质量足够令人满意,那么认真遵循遗留模型可以消除所有集成成本。在向已建立的、占主导地位的遗留系统添加外围扩展的情况下,必须认真考虑遵循遗留模型的方法。由于我们不可抗拒地热衷于重新发明轮子,浪费时间和金钱去制定几乎没有商业价值的解决方案,这种选择被严重低估了。
3- 反腐败层:一种高度防御性的策略,将您的模型与遗留模型的腐败隔离开来。
4- 替换遗留系统:最不理想的选择。开发人员应避免在开发新模型的同时逐步淘汰遗留系统的大型复杂模型(尤其是当这些模型属于系统的核心域时),因为这些雄心勃勃的尝试更容易导致不良后果。
在尝试反腐败层解决方案之前,您应该仔细重新考虑选项 1 和 2;反腐败层除了会构成要添加到组织上下文图中的新元素,而这些新元素又需要维护和支持之外,还可能变得非常丑陋和复杂。在讨论反腐败层模式之前,让我们简单讨论一下两种著名的设计模式:
正面:
外观模式 (Façade) 是一种用于为复杂系统提供更简洁接口的模式。外观模式应该遵循所连接系统的模型,并且避免引入新的模型(即:它们应该使用相同的语言)。
想象一下具有以下界面的旅行计划系统:
public interface ITripService
{
void BookAirline();
void ReserveHotelRoom();
void ReserveTouristAttractionTickets();
}
当需要时,可以通过以下 Façade 提供更简单的界面来协调整个操作的执行:
public interface ITripServiceFacade
{
void PlanItinerary();
}
适配器:
适配器是一种模式,它通过提供客户端能够理解的接口,允许两个不兼容的抽象之间进行通信。适配器可以使用客户端的语言,并将其适配到其他子系统。
想象一下具有以下界面的叫车应用程序:
public interface ITransportationService
{
void StartTrip(string country, Zipcode from, Zipcode to);
}
然而,当人们发现在某些国家/地区,行程应该基于出发地和目的地的精确坐标而不是邮政编码来制定时,便提供了一个适配器:
public interface ITransportationServiceAdapter
{
void StartTrip(Coordinates from, Coordinates to);
}
请注意,适配器使用客户端的语言,并负责向适配者发起相应的请求。
反腐败层:
防腐层 (Anti-Corruption Layer) 结合了一系列外观层 (Façade)、适配器 (Adapter) 和转换器 (Translator),用于隔离模型,避免其需要集成的其他模型造成损坏。如果两个模型之间存在客户-供应商关系,则防腐层可能是单向的;否则,防腐层可能是双向的。
为了说明这一模式,我们首先考虑一个电商应用,该应用通过估算订单所需包裹的数量和尺寸来显示订单的运费,然后使用复杂的网络遍历算法来找到最便宜的运输路线。由此,我们可以快速推断出两个不同的场景:运输和包装,它们各自拥有不同的功能和完全独立的模型。
事实证明,该组织已经拥有一个使用多年的旧版包装服务——因为该组织在将战略转向电子商务之前,曾专注于包装行业。架构师仔细研究了旧版包装服务后,意识到与旧版服务的集成是不可避免的,因为该服务非常复杂(这表明大量的知识被融入到旧版服务中),因此放弃旧版服务或尝试大规模替换都是难以操作的解决方案。进一步的检查也排除了墨守成规的做法,因为旧版模型已经非常过时,并且其假设不再适用于当前的业务模型,因此将运输环境与旧版包装模型相集成会导致运输方面的高腐败率。
遗憾的是,包装服务过于老旧,缺乏文档记录,并且臭名昭著地存在大量 bug,迫使系统其他组件不得不采取一些临时解决方案。此外,最初编写该服务的开发人员已经离职,导致运输团队完全独立。因此,架构师决定将运输上下文与包装上下文集成,并创建一个反腐败层,将运输模型与旧版本隔离开来。
让我们仔细看看传统的包装模型,以突出模型的弱点以及它们如何破坏运输模型:
namespace Warehouse.Packaging
{
public class Container
{
//Irrelevant details
}
public class Package
{
public double LengthInInches { get; }
public double WidthInInches { get; }
public double HeightInInches { get; }
}
public class Item
{
public double LengthInInches { get; }
public double WidthInInches { get; }
public double HeightInInches { get; }
}
public class Warehouse
{
public string Code { get; }
public string ZipCode { get; }
public IEnumerable<Container> AvailableContainers { get; }
//Further details of a messy model
}
public interface ILegacyPackagingService
{
bool ValidateItemDimensions (IEnumerable<Item> items);
IEnumerable<Package> PackageItems (IEnumerable<Item> items, Warehouse warehouse);
IEnumerable<Package> OptimizePackaging (IEnumerable<Package> packages);
}
}
上述领域模型存在几个问题:
1-ILegacyPackagingService
提供了一个复杂的界面,其中验证、包装和优化作为三个独立的 API 提供 - 可能有充分的理由 - 然而这并不适合运输模型的需求,因为从运输的角度来看,所有这些操作都被视为单一操作。
2-Item
和Package
模型使用英制计量系统,这与组织范围内在所有新模型中采用公制的新政策存在冲突。
3-包装模块中的仓库模型已经非常过时,不再反映企业如何对其自己的仓库进行建模。
4-PackageItems
中定义的 APIILegacyPackagingService
引入了包装逻辑与模型之间的耦合Warehouse
——因为包装之前被视为只能在组织指定的仓库之一内进行的操作。然而,当前的运输模型无法维持这样的假设,因为它允许产品直接从外部供应商的仓储设施发货。
显然,反腐败层还有很多工作要做。如果上述任何问题渗入到运输模型中,都会导致模型损坏,并带有遗留系统的所有弱点。为了缓解第一个问题,我们在反腐败层中引入了一个外观层:
namespace Warehouse.Packaging.AntiCorruption.Shipping
{
public interface IPackagingServiceFacade
{
IEnumerable<Warehouse.Packaging.Package> PackageItems (IEnumerable<Item> items, Warehouse warehouse);
}
public class PackagingServiceFacade : IPackagingServiceFacade
{
private readonly ILegacyPackagingService _packagingService;
public PackagingServiceFacade (ILegacyPackagingService packagingService)
{
_packagingService = packagingService;
}
public IEnumerable<Warehouse.Packaging.Package> PackageItems (IEnumerable<Item> items, Warehouse warehouse)
{
if (_packagingService.ValidateItemDimensions (items))
{
var packages = _packagingService.PackageItems (items, warehouse);
var optimizedPackages = _packagingService.OptimizePackaging (packages);
return optimizedPackages;
}
return Enumerable.Empty<Package> ();
}
}
}
请注意,Façade 使用与 Packaging 服务相同的模型,未添加任何模型元素,且语言保持不变。Façade 仅提供更友好的模型接口。Façade 甚至位于Warehouse.Packaging
包含 Packaging 上下文的模块/命名空间下(反腐败层可以跨越多个模块)。
为了解决第二个问题,反腐败层将定义一个转换器对象来映射两个上下文中使用的不同模型,在下面的例子中,我们使用 AutoMapper 以简单的方式说明映射操作:
namespace Logistics.Shipping.AntiCorruption.Packaging
{
public class PackagerProfile : Profile
{
private const double InchesCmConversionConst = 0.39370;
public PackagerProfile()
{
CreateMap<Product, Item>()
.ForMember(dst => dst.LengthInInches, opt => opt.MapFrom(src => src.LengthInCm * InchesCmConversionConst))
.ForMember(dst => dst.WidthInInches, opt => opt.MapFrom(src => src.WidthInCm * InchesCmConversionConst))
.ForMember(dst => dst.HeightInInches, opt => opt.MapFrom(src => src.HeightInCm * InchesCmConversionConst));
CreateMap<Warehouse.Packaging.Package, Logistics.Shipping.Package>()
.ForMember(dst => dst.LengthInCm, opt => opt.MapFrom(src => src.LengthInInches / InchesCmConversionConst))
.ForMember(dst => dst.WidthInCm, opt => opt.MapFrom(src => src.WidthInInches / InchesCmConversionConst))
.ForMember(dst => dst.HeightInCm, opt => opt.MapFrom(src => src.HeightInInches / InchesCmConversionConst));
}
}
}
可以来回使用转换器来映射不兼容的模型。
为了处理第三和第四个问题,我们在反腐败层引入了一个适配器,它为运输服务提供了一个与Warehouse
模型松散耦合的更兼容的接口,而是使用Container
值对象来表示包装能力:
namespace Logistics.Shipping.AntiCorruption.Packaging
{
public interface IPackagingService
{
IEnumerable<Logistics.Shipping.Package> PackageItems (IEnumerable<Product> items, IEnumerable<Container> containers);
}
public class PackagingServiceAdapter : IPackagingService
{
private readonly IPackagingServiceFacade _packagingServiceFacade;
private readonly IMapper _mapper;
public PackagingServiceAdapter (IPackagingServiceFacade packagingServiceFacade, IMapper mapper)
{
_packagingServiceFacade = packagingServiceFacade;
_mapper = mapper;
}
public IEnumerable<Logistics.Shipping.Package> PackageItems (IEnumerable<Product> products, IEnumerable<Container> containers)
{
Warehouse warehouse; //Logic to initialize a transient dummy warehouse
var items = _mapper.Map<IEnumerable<Item>> (products); //Using the translator to map the Product model to an Item
var packages = _packagingServiceFacade.PackageItems (items, warehouse); //Calling the Façade
return _mapper.Map<IEnumerable<Logistics.Shipping.Package>> (packages); //Mapping the Packager's result to the Package model defined in the Shipping context
}
}
}
结果是运输模型与传统包装服务集成在一起,而不会被传统服务破坏:
namespace Logistics.Shipping
{
public class Zone
{
}
public class Product
{
public double LengthInCm { get; }
public double WidthInCm { get; }
public double HeightInCm { get; }
}
public class Package
{
public double LengthInCm { get; }
public double WidthInCm { get; }
public double HeightInCm { get; }
}
public class Courier
{
private IPackagingService _packagingService;
public double GetQuote (IEnumerable<Product> items, Zone source, Zone destination)
{
var packagingCapabilities = _capabilitiesService.GetPackagingCapabilities (source);
var packages = _packagingService.PackageItems (items, packagingCapabilities.Containers);
//Shipping specific logic goes here
}
}
}
领域模型的粗略图如下:
为了简单起见,省略了有关反腐败层的细节。
结论
开发反腐层是一个耗时的过程,需要大量的分析和开发工作。架构师不应立即采用此解决方案,而应考虑其他简单的替代方案。然而,当别无选择时,反腐层就像一道环绕堡垒的城墙,可以保护您的模型免受外部影响,并允许您独立工作。
鏂囩珷鏉ユ簮锛�https://dev.to/asarnaout/the-anti-corruption-layer-pattern-pcd