The Anti-Corruption Layer Pattern
Tags: architecture, designpatterns
Published: 2018-11-11 10:05:42

---
title: The Anti-Corruption Layer Pattern
published: true
description: Isolating your model from legacy systems using an Anti-Corruption Layer.
tags: Architecture, Design Patterns
cover_image: https://thepracticaldev.s3.amazonaws.com/i/z9hmm35y8l4yxmh3n6k0.jpg
---

It is no secret that integrating with legacy systems is a no fun milestone. Poor documentation, lack of support, messy interfaces and more than a handful of dormant bugs are just a subset of the problems you might face in the integration process. Yet more than often, integration is an absolute necessity for technical and/or political reasons. Integration with legacy systems imposes a risk on the system under design since legacy models are usually poorly designed and thus your new, well designed model is very likely to be corrupted by the legacy integration.

# Integration Strategies:

Developers who need to work with legacy systems are faced with four choices:

1- Ditch the legacy: Having a common use case isn't always a justification for integration. Developers should consider this option by scrutinizing the value of the legacy system and weighing it in against the cost of integration.

2- Conform to the legacy: When the organization has no plans to replace the legacy system, and when the quality of the legacy model is acceptable enough, then diligently conforming to the legacy model would eliminate all integration costs. A conformist approach must be seriously considered in situations where peripheral extensions are being added to a well established, dominant legacy system. This choice is highly underrated due to our irresistible infatuation with re-inventing the wheel and wasting time and money working out solutions that deliver little to no business value.

3- Anti-Corruption Layer: A highly defensive strategy to isolate your model from corruption by legacy models.

4- Replacing the Legacy: The least favorable option. Developers should refrain from phasing out large and complex models of a legacy system (specially when these models belong to the system's Core Domain) while working simultaneously on the new model, as these ambitious attempts are more susceptible to undesired outcomes.


Before attempting an Anti-Corruption Layer solution, you should carefully rethink options 1 and 2; Anti-Corruption layers can get very ugly and complicated in addition to the fact that they compose new elements to be added to the organization's context map that require maintenance and support. Before approaching the Anti-Corruption Layer pattern let's talk briefly about two prominent design patterns:


## Façade:

A Façade is a pattern that is used to provide a simpler interface to a complex system. The Façade should adhere to the interfaced system's model and refrain from introducing a new model of its own (i.e: They should speak the same language). 

Think of a trip planning system that has the following interface:

```csharp
public interface ITripService
{
	void BookAirline();
	void ReserveHotelRoom();
	void ReserveTouristAttractionTickets();
}
```

A simpler interface could be provided -when needed- through the following Façade which co-ordinates the execution of the entire operation:

```csharp
public interface ITripServiceFacade
{
	void PlanItinerary();
}
```


## Adapter:

![A Charger Adapter](https://thepracticaldev.s3.amazonaws.com/i/31jsqekdxozo1p4gf121.png)
<center>A Wall Adapter</center>

An Adapter is a pattern which allows communication between two incompatible abstractions by providing an interface that the client can make sense of. The adapter is allowed to speak the language of the client and adapt it to the other sub-system.

Think of a ride hailing application having the following interface:

```csharp
public interface ITransportationService
{
	void StartTrip(string country, Zipcode from, Zipcode to);
}
```

However, when insight emerged that - in some countries - trips should be fared based on the exact co-ordinates of the source and destination locations instead of zipcodes, an Adapter was provided:


```csharp
public interface ITransportationServiceAdapter
{
	void StartTrip(Coordinates from, Coordinates to);
}
```

Notice that the Adapter speaks the language of the client and is responsible for initiating the corresponding request to the adaptee.


# Anti-Corruption Layer:

![The Great Fire](https://thepracticaldev.s3.amazonaws.com/i/mvi9162honjdj2js6njt.jpg)
<center>The Great Fire engulfing the city of Rome</center>

An Anti-Corruption Layer combines a set of Façades, Adapters and Translators to isolate a model from corruption by other models it needs to integrate with. The Anti-Corruption Layer might be uni-directional in case of the existence of a Customer-Supplier relationship between the two models or bi-directional otherwise.

To illustrate the pattern, let's first consider an e-Commerce application that displays an order's shipping costs by estimating the number and dimensions of the packages required to ship the order then uses a complex network traversal algorithm to find the cheapest route for shipping. From this we can quickly deduce two distinct contexts: Shipping and Packaging, each with different capabilities and a completely independent model. 

Turns out that the organization already owns a legacy Packaging service that has been used for years - since the organization has previously specialized in the packaging industry before shifting the strategy towards e-Commerce. As the architects closely examined the legacy Packaging service, they realized that integration with the legacy is inevitable since the service is highly complicated (indicating that a-lot of knowledge has been crunched into the legacy) and therefore abandoning the legacy or attempting a large scale replacement are unwieldy solutions. A closer examination also ruled out a conformist approach since the legacy model is very outdated and made assumptions that do not fit anymore into the current business model, therefore conforming the Shipping context to the legacy Packaging model would cause a high rate of corruption on the Shipping side.

Unfortunately, the Packaging service is too old, undocumented, and is notorious for a number of bugs that forced several ad hoc workarounds across other components in the system, moreover, the original developers who wrote the service have departed from the organization leaving the Shipping team completely on its own. At this point, the architects decided that the Shipping context should be integrated with the Packaging context with an Anti-Corruption layer isolating the Shipping model from the legacy.

Let's take a closer look to the legacy Packaging model to highlight the model weaknesses and how they can corrupt the Shipping model:

```csharp
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);
    }
}
```

Several problems exist in the above domain model:

1- The `ILegacyPackagingService` offers a complex interface where Validation, Packaging and Optimization are provided as three separate APIs - for a good reason probably - however this doesn't fit well with the Shipping model's needs since all of these operations are viewed as a single operation from the Shipping's point of view.

2- The `Item` and `Package` models use the Imperial System of measurement which presents a conflict with a new organization-wide policy to adopt the metric system in all new models.

3- The Warehouse model in the Packaging module is highly outdated and no longer reflects how the business models its own Warehouses.

4- The `PackageItems` API defined in the `ILegacyPackagingService` introduces a coupling between the Packaging logic and the `Warehouse` model - since packaging was previously viewed as an operation that can only take place within one of the organization's designated warehouses. However, the current Shipping model cannot sustain such an assumption since it allows products to be shipped directly from the external Vendors' storage facilities.


Obviously, the Anti-Corruption Layer has a-lot of work to do. Allowing any of the above mentioned problems to leak into the Shipping model would yield a corrupted model that carries all the weaknesses of the legacy system. To mitigate the first issue, we introduce a Façade within the Anti-Corruption Layer:

```csharp
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> ();
        }
    }
}
```

Note that the Façade uses the same model as the Packaging service, no model elements were added and the language is maintained. The Façade only offers a friendlier interface to the model. The Façade is even placed under the `Warehouse.Packaging` module/namespace which encompasses the Packaging context (Anti-Corruption layers can span multiple modules).

To account for the second problem, the Anti-Corruption layer will define a translator object to map between the different models used in the two contexts, in the following example we are using AutoMapper to illustrate the mapping operation in a simple manner:

```csharp
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));
        }
    }
}
```

The translator can be used back and forth to map the incompatible models.

To handle the third and forth issues, we introduce an Adapter to the Anti-Corruption layer which provides the Shipping service with a more compatible interface that is loosely coupled from the `Warehouse` model and instead uses the `Container` value object to represent the packaging capabilities:

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

The outcome is a Shipping model that is integrated with the legacy Packaging service without being corrupted by the legacy:

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

A rough map of the domain model would be as follows:

![Domain Model](https://thepracticaldev.s3.amazonaws.com/i/r4fjbtv8kxo9vb8fkls9.png)

Details concerning the Anti-Corruption layer are omitted for simplicity.
 

# Conclusion

![Great Wall of China](https://thepracticaldev.s3.amazonaws.com/i/mtr4tfi06tyj6czmbyb9.jpg)
<center>The Great Wall of China</center>

Developing an Anti-Corruption layer is a time consuming process that requires lots of analysis and development work. Architects should not jump to this solution at once and instead consider other simple alternatives, however when there is no other choices, an Anti-Corruption layer - just like a wall surrounding a fortress from hostile invasion - will protect your model from outer influences and allow you to work in isolation.