防腐層模式

banq發表於2018-11-12

與遺留舊系統整合是一個無趣的荒路,這已不是什麼秘密:糟糕的文件,缺乏支援,雜亂的介面以及少數幾個錯誤只是在整合過程中可能遇到的問題的一個子集。然而,出於技術和/或政治原因,整合是絕對必要的。與遺留系統的整合會對設計中的系統造成風險,因為傳統模型通常設計不當,會對你新設計良好的模型很可能被遺留整合破壞。

整合策略
防腐層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
        }
    }
}


結論
開發防腐層是一個耗時的過程,需要大量的分析和開發工作。架構師不應該立刻跳到這個解決方案,而是首先考慮其他簡單的替代方案,但是當沒有其他選擇時,防腐層 - 就像圍繞堡壘的牆壁進行入侵 - 將保護您的模型免受外部影響並讓你獨立地工作。

相關文章