防腐层模式

18-11-12 banq
                   

与遗留旧系统集成是一个无趣的荒路,这已不是什么秘密:糟糕的文档,缺乏支持,杂乱的界面以及少数几个错误只是在集成过程中可能遇到的问题的一个子集。然而,出于技术和/或政治原因,集成是绝对必要的。与遗留系统的集成会对设计中的系统造成风险,因为传统模型通常设计不当,会对你新设计良好的模型很可能被遗留集成破坏。

集成策略

防腐层Anti-Corruption是一种高度防御性的策略,可以通过传统模型将您的模型与腐败隔离开来。防腐层结合了一组模式:门面facade模式和适配器模式。将模型与其需要集成的其他模型隔离开来。

如果两个模型之间存在Customer-Supplier关系,则防腐层可能是单向的,或者是双向的。

为了说明这种模式,我们首先考虑一个电子商务应用程序,它通过估计发送订单所需的包的数量和尺寸来显示订单的运输成本,然后使用复杂的网络遍历算法来查找最便宜的运输路线。由此我们可以快速推断出两个不同的背景:运输和包装,每个都有不同的功能和完全独立的模型。

事实证明,该公司已经拥有多年使用的传统包装服务 - 因为该组织之前专注于包装行业,然后才将战略转向电子商务。当架构师仔细研究传统的包装服务时,他们意识到与传统的集成是不可避免的,因为服务非常复杂(表明许多知识已经被遗留下来),因此放弃遗产或大规模尝试更换旧系统是笨重的解决方案。仔细研究也排除了一种完全依旧使用旧系统的方法,因为遗留模型已经过时,并不再适合当前的商业模式。

遗憾的是,打包服务太旧,没有文档记录,并且因为许多错误而臭名昭着,这些错误迫使系统中的其他组件进行了多次临时的解决方案实施,此外,编写该服务的原始开发人员已离开组织,运输团队完全只能靠自己。此时,架构师决定将运输上下文与包装上下文集成在一起,防腐层将运输模型与遗留模式隔​​离开来。

让我们仔细研究传统的包装模型,才能了解其突出模型的弱点以及它们如何破坏运输模型:

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提供 - 可能有很好的理由 - 但是这不符合运输Shipping模型的需要,因为从运输角度看,所有这一系列操作可被视为一个单独的操作。

2- Item和Package模型使用英制测量系统,这与所有新模型中采用公制系统的新组织范围政策存在冲突。

3-Package模块中的仓库模型高度过时,不再反映业务如何为自己的仓库建模。

4- PackageItems定义的API:ILegacyPackagingService引入了包装逻辑和Warehouse模型之间的耦合- 因为包装默认被视为只能在公司的指定仓库中进行的操作。但是,目前的Shipping模型无法维持这样的假设前提,因为它允许产品直接从外部供应商的仓储设施发货。

外观facade模式

显然,防腐层有很多工作要做。把任何上述问题泄漏到Shipping模型中将产生损坏的模型,该模型承载遗留系统的所有弱点。为了缓解第一个问题,我们在防腐层中引入了一个外观facade:

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上下文(防腐层可以跨越多个模块)。

转换器translator模式

为了解决第二个问题,防腐层将定义一个转换器对象,这样在两个上下文中使用的不同模型之间进行映射,在下面的示例中,我们使用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));
        }
    }
}

可以来回使用转换器来映射不兼容的模型。

适配器Adapter模式

为了处理第三个和第四个问题,我们向防腐层引入了一个适配器,它为Shipping运输服务提供了一个更兼容的接口,该接口与Warehouse模型松散耦合,而是使用Containervalue对象来表示包装功能:

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
        }
    }
}

与传统打包服务集成的Shipping模型,不会受到遗留损坏:

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
        }
    }
}

结论

开发防腐层是一个耗时的过程,需要大量的分析和开发工作。架构师不应该立刻跳到这个解决方案,而是首先考虑其他简单的替代方案,但是当没有其他选择时,防腐层 - 就像围绕堡垒的墙壁进行入侵 - 将保护您的模型免受外部影响并让你独立地工作。