The Ultimate Anti-Corruption Layer (ACL) Design Pattern Cheat Sheet

Introduction: What is an Anti-Corruption Layer?

An Anti-Corruption Layer (ACL) is a design pattern that creates a boundary between different subsystems or services, particularly when integrating with legacy systems, third-party services, or domains with conflicting models. The pattern was introduced in Eric Evans’ Domain-Driven Design book and serves as a protective interface that translates between incompatible models while isolating your domain from external influences. ACLs prevent foreign concepts from “corrupting” your carefully designed domain model, preserving its integrity and allowing the different systems to evolve independently.

Core Concepts of the Anti-Corruption Layer

Key Principles

  • Model Isolation: Prevents foreign domain models from infiltrating your domain
  • Translation: Converts between different models, protocols, and data formats
  • Encapsulation: Hides complexity of interacting with external systems
  • Risk Mitigation: Reduces impact of changes in external systems
  • Interface Stability: Provides a stable interface to your domain
  • Dependency Inversion: Reverses control direction through abstractions

When to Use an ACL

ScenarioIndicatorsACL Benefit
Legacy System IntegrationOld system, poor documentation, complex interfacesIsolates complexity, avoids contaminating new model
Third-Party Service IntegrationDifferent conceptual models, external controlShields domain from external concepts
Bounded Context BoundariesDifferent teams, different domain vocabulariesClarifies translation between domains
Disparate Technology StacksDifferent protocols, formats, technologiesEncapsulates technology conversion
Evolving SystemsDifferent change rates, different lifecyclesDecouples evolution paths
Organizational BoundariesDifferent companies, different teamsClarifies ownership boundaries
Strategic vs. Commodity CodeCore vs. support functionalityProtects strategic, simplifies commodity

When NOT to Use an ACL

  • Simple data passing with minimal translation
  • When teams share the same bounded context/model
  • For temporary integrations with short lifespans
  • When performance requirements are extremely stringent
  • When conceptual models are already closely aligned

ACL Architectural Patterns

1. Facade Pattern

Description: Provides a simplified interface to a complex subsystem.

Structure:

Client -> Facade -> Complex Subsystem Components

Implementation Example:

// Complex legacy API with many methods
class LegacyInventorySystem {
    public void checkItem(int id) { /*...*/ }
    public void reserveItem(int id, int qty) { /*...*/ }
    public void processPayment(int id, double amount) { /*...*/ }
    // Many more methods...
}

// ACL Facade simplifying the interaction
class InventoryFacade {
    private LegacyInventorySystem legacy;
    
    public InventoryFacade(LegacyInventorySystem legacy) {
        this.legacy = legacy;
    }
    
    public boolean purchase(Product product, int quantity) {
        legacy.checkItem(product.getId());
        legacy.reserveItem(product.getId(), quantity);
        legacy.processPayment(product.getId(), product.getPrice() * quantity);
        return true;
    }
}

Benefits:

  • Simplifies complex interfaces
  • Hides implementation details
  • Reduces coupling to legacy system

2. Adapter Pattern

Description: Converts the interface of a class into another interface clients expect.

Structure:

Client -> Adapter -> Adaptee (External System)

Implementation Example:

// Your domain interface
interface PaymentProcessor {
    PaymentResult processPayment(Payment payment);
}

// External payment service with different interface
class ExternalPaymentService {
    public ResponseDTO submitTransaction(TransactionDTO txn) {
        // External implementation
        return new ResponseDTO();
    }
}

// Adapter implementing your interface but using external service
class PaymentServiceAdapter implements PaymentProcessor {
    private ExternalPaymentService externalService;
    
    public PaymentServiceAdapter(ExternalPaymentService service) {
        this.externalService = service;
    }
    
    @Override
    public PaymentResult processPayment(Payment payment) {
        // Convert domain Payment to external TransactionDTO
        TransactionDTO txn = new TransactionDTO();
        txn.setAmount(payment.getAmount().getValue());
        txn.setCurrency(payment.getAmount().getCurrency().getCode());
        txn.setCardToken(payment.getCardDetails().getToken());
        
        // Call external service
        ResponseDTO response = externalService.submitTransaction(txn);
        
        // Convert external ResponseDTO to domain PaymentResult
        return new PaymentResult(
            response.isSuccessful(), 
            response.getAuthCode(),
            mapErrorCode(response.getErrorCode())
        );
    }
    
    private ErrorType mapErrorCode(String externalErrorCode) {
        // Map external error codes to domain error types
        if ("INSUF_FUNDS".equals(externalErrorCode)) {
            return ErrorType.INSUFFICIENT_FUNDS;
        }
        // Other mappings...
        return ErrorType.UNKNOWN;
    }
}

Benefits:

  • Precise translation between different interfaces
  • Allows reuse of existing functionality
  • Domain remains unaffected by external interfaces

3. Service Layer Pattern

Description: Defines an application’s boundary and its set of available operations from the perspective of client layers.

Structure:

Client -> Service Layer -> Domain Layer
                        -> Infrastructure/External Systems

Implementation Example:

class OrderService {
    private OrderRepository orderRepository;
    private PaymentProcessor paymentProcessor;
    private LegacyShippingAdapter shippingAdapter;
    
    // Constructor with dependencies...
    
    public OrderConfirmation placeOrder(OrderRequest request) {
        // Create domain objects
        Order order = new Order(request.getCustomerId());
        
        // Add items from request
        for (OrderItemRequest itemRequest : request.getItems()) {
            Product product = productRepository.findById(itemRequest.getProductId());
            order.addItem(product, itemRequest.getQuantity());
        }
        
        // Process payment
        Payment payment = new Payment(order.getTotalAmount(), request.getPaymentDetails());
        PaymentResult paymentResult = paymentProcessor.processPayment(payment);
        
        if (!paymentResult.isSuccessful()) {
            throw new PaymentFailedException(paymentResult.getErrorType());
        }
        
        // Save order
        orderRepository.save(order);
        
        // Create shipping request via adapter to legacy system
        ShippingResult shippingResult = shippingAdapter.createShipment(order);
        
        // Return confirmation
        return new OrderConfirmation(
            order.getId(),
            paymentResult.getAuthorizationCode(),
            shippingResult.getTrackingNumber(),
            order.getEstimatedDeliveryDate()
        );
    }
}

Benefits:

  • Provides clear API for application functionality
  • Coordinates between multiple external systems
  • Handles transaction boundaries

4. Gateway Pattern

Description: Encapsulates access to an external system or resource.

Structure:

Client -> Gateway Interface -> Gateway Implementation -> External System

Implementation Example:

// Gateway interface defined in your domain
interface CustomerProfileGateway {
    CustomerProfile getProfile(CustomerId id);
    void updateProfile(CustomerProfile profile);
}

// Implementation in infrastructure layer
class LegacyCRMCustomerGateway implements CustomerProfileGateway {
    private LegacyCRMClient crmClient;
    private CustomerProfileTranslator translator;
    
    public LegacyCRMCustomerGateway(LegacyCRMClient crmClient, CustomerProfileTranslator translator) {
        this.crmClient = crmClient;
        this.translator = translator;
    }
    
    @Override
    public CustomerProfile getProfile(CustomerId id) {
        // Call legacy CRM system
        CRMCustomerRecord record = crmClient.getCustomer(id.toString());
        
        // Translate to domain model
        return translator.toDomain(record);
    }
    
    @Override
    public void updateProfile(CustomerProfile profile) {
        // Translate from domain model
        CRMCustomerRecord record = translator.fromDomain(profile);
        
        // Update in legacy CRM
        crmClient.updateCustomer(record);
    }
}

Benefits:

  • Abstracts external system details
  • Allows swapping implementations
  • Defines domain-friendly interface

5. Mapper Pattern

Description: Transforms data between incompatible domain models.

Structure:

Domain A -> Mapper -> Domain B

Implementation Example:

// Mapper from legacy DTO to domain model
class CustomerProfileTranslator {
    public CustomerProfile toDomain(CRMCustomerRecord record) {
        Address address = new Address(
            record.getStreet(),
            record.getCity(),
            record.getState(),
            new ZipCode(record.getZipCode())
        );
        
        CustomerProfile profile = new CustomerProfile(
            new CustomerId(record.getId()),
            new PersonName(record.getFirstName(), record.getLastName()),
            new EmailAddress(record.getEmailAddress()),
            address
        );
        
        if (record.getPreferredContactMethod().equals("EMAIL")) {
            profile.setContactPreference(ContactPreference.EMAIL);
        } else if (record.getPreferredContactMethod().equals("PHONE")) {
            profile.setContactPreference(ContactPreference.PHONE);
        } else {
            profile.setContactPreference(ContactPreference.MAIL);
        }
        
        return profile;
    }
    
    public CRMCustomerRecord fromDomain(CustomerProfile profile) {
        CRMCustomerRecord record = new CRMCustomerRecord();
        record.setId(profile.getId().toString());
        record.setFirstName(profile.getName().getFirstName());
        record.setLastName(profile.getName().getLastName());
        record.setEmailAddress(profile.getEmail().toString());
        record.setStreet(profile.getAddress().getStreet());
        record.setCity(profile.getAddress().getCity());
        record.setState(profile.getAddress().getState());
        record.setZipCode(profile.getAddress().getZipCode().toString());
        
        switch (profile.getContactPreference()) {
            case EMAIL:
                record.setPreferredContactMethod("EMAIL");
                break;
            case PHONE:
                record.setPreferredContactMethod("PHONE");
                break;
            case MAIL:
                record.setPreferredContactMethod("MAIL");
                break;
        }
        
        return record;
    }
}

Benefits:

  • Clear separation between domain models
  • Explicit transformation rules
  • Enables domain model purity

Implementation Strategies

1. Layered ACL

Description: Divides the ACL into multiple layers for complex translations.

LayerResponsibility
Protocol AdapterHandles communication protocol details (HTTP, gRPC, etc.)
DTO ConverterMaps between external DTOs and internal DTOs
Domain TranslatorConverts between internal DTOs and domain objects
Service FacadeOrchestrates the overall interaction

Example Structure:

Client -> Service Facade -> Domain Translator -> DTO Converter -> Protocol Adapter -> External System

When to Use:

  • For complex external systems with multiple integration points
  • When dealing with multiple transformation concerns
  • When protocol, data format, and domain concepts all differ

2. Proxy-Based ACL

Description: Uses a proxy service to encapsulate and transform the external API.

ComponentResponsibility
Proxy ServiceIndependent service that wraps the external system
API GatewayRoutes requests and performs basic transformations
Caching LayerReduces load on external system
Circuit BreakerHandles failures and timeouts

Example Architecture:

Your System -> API Gateway -> Proxy Service -> External System

When to Use:

  • For very complex or brittle legacy systems
  • When multiple clients need the same integration
  • When performance or availability concerns exist
  • For cloud-based or distributed system architectures

3. Event-Driven ACL

Description: Uses events to decouple systems with different models.

ComponentResponsibility
Event TranslatorConverts between domain events and external events
Message BrokerHandles reliable delivery of events
Event HandlerProcesses incoming events and updates domain
Event PublisherPublishes domain events to external systems

Example Flow:

External Event -> Message Broker -> Event Translator -> Domain Event Handler -> Domain Model

When to Use:

  • For asynchronous integration patterns
  • When real-time consistency isn’t required
  • To further reduce coupling between systems
  • When event sourcing is already part of your architecture

4. Domain-Specific Language (DSL) Based ACL

Description: Creates a dedicated language for translation between domains.

ComponentResponsibility
DSL InterpreterProcesses DSL expressions
Transformation RulesDomain-specific rules defined in DSL
Rule EngineApplies rules to transform between models

Example Rule (Pseudocode):

WHEN ExternalCustomer.status == "A" 
THEN Customer.state = ACTIVE

WHEN ExternalCustomer.status == "S" 
THEN Customer.state = SUSPENDED

MAP ExternalCustomer.addr_line1 TO Customer.address.streetLine
MAP ExternalCustomer.city TO Customer.address.city

When to Use:

  • For extremely complex translations
  • When business experts need to define transformations
  • When transformations change frequently
  • For reusable transformation patterns

Step-by-Step Implementation Guide

1. Analysis Phase

  • [ ] Identify the bounded context boundaries
  • [ ] Document the external system’s domain model
  • [ ] Document your domain model
  • [ ] Identify key translation points and conflicts
  • [ ] Determine integration patterns (sync, async, batch)
  • [ ] Assess performance and reliability requirements

2. Design Phase

  • [ ] Choose appropriate ACL patterns for each integration point
  • [ ] Define interfaces that align with your domain
  • [ ] Design translation/mapping strategy
  • [ ] Plan error handling and resilience approach
  • [ ] Consider monitoring and observability
  • [ ] Document translation rules for future reference

3. Implementation Phase

  • [ ] Create domain interfaces in your bounded context
  • [ ] Implement the ACL components
  • [ ] Develop comprehensive tests for translations
  • [ ] Implement error handling and logging
  • [ ] Set up monitoring for ACL performance
  • [ ] Document the final implementation

4. Maintenance Phase

  • [ ] Monitor for changes in the external system
  • [ ] Evolve the ACL as your domain model changes
  • [ ] Refactor when translation rules become too complex
  • [ ] Consider replacing or eliminating the ACL if systems converge

Common Challenges and Solutions

ChallengeSymptomsSolutions
Complex TranslationsLarge, unwieldy mapper classesBreak into smaller mappers; use composition; introduce intermediate representations
Performance BottlenecksSlow response times, timeoutsCaching; batch processing; optimization of translation code; parallel processing
External System ChangesUnexpected failures after external updatesComprehensive testing; versioned APIs; monitoring; defensive programming
Error HandlingCascading failures; lost dataCircuit breakers; retry mechanisms; dead letter queues; compensating transactions
Bidirectional ConsistencyData drift between systemsEvent sourcing; conflict resolution strategies; reconciliation processes
Growing ComplexityExpanding ACL code; maintainability issuesRegular refactoring; clear boundaries; documentation; team knowledge sharing
Testing DifficultiesDifficult to test interactionsMock external systems; contract testing; integration testing environments

ACL Design Patterns Comparison Table

PatternStrengthsWeaknessesBest Use Cases
FacadeSimple to implement; hides complexityMay become a “god class”; less flexibilityWhen simplifying a complex API without much transformation
AdapterClear separation of concerns; flexibleCan proliferate for many interfacesWhen domain and external interfaces differ significantly
Service LayerGood orchestration; clear APICan become bloated; mixed responsibilitiesWhen coordinating multiple external services
GatewayClean domain abstraction; swappable implementationsAdditional indirectionWhen domain needs clean abstractions of external resources
MapperExplicit transformations; maintainableVerbose for complex mappingsWhen models differ substantially and need explicit translation
Layered ACLSeparation of concerns; maintainableComplexity; performance overheadFor complex integrations with multiple concerns
Proxy-BasedComplete isolation; reusabilityDeployment complexity; latencyWhen multiple systems need the same integration
Event-DrivenLoose coupling; scalabilityEventually consistent; complexityFor asynchronous, resilient integrations
DSL-BasedBusiness-readable rules; maintainableLearning curve; implementation effortWhen transformations are complex and change frequently

Best Practices for ACL Implementation

1. Design Principles

  • Single Responsibility: Each ACL component should handle one aspect of translation
  • Interface Segregation: Define clean, minimal interfaces aligned with domain needs
  • Dependency Inversion: ACL should depend on domain abstractions, not vice versa
  • Don’t Repeat Yourself: Reuse translation logic where appropriate
  • Keep It Simple: Avoid over-engineering for simple translations

2. Coding Guidelines

  • Create clear separation between domain code and ACL code
  • Use descriptive naming to indicate external vs. domain concepts
  • Document translation rules, especially non-obvious ones
  • Add comprehensive unit tests for all translations
  • Include logging at integration points for troubleshooting
  • Implement proper error handling and fallback mechanisms
  • Consider validation on both ingress and egress

3. Architectural Recommendations

  • Place ACL in appropriate architectural layer (usually infrastructure)
  • Keep ACL implementations behind domain interfaces
  • Consider scalability implications for high-volume integrations
  • Implement circuit breakers for critical external dependencies
  • Include monitoring for ACL performance and error rates
  • Consider caching strategies where appropriate
  • Document the ACL architecture and design decisions

Real-World Examples

Example 1: E-commerce Integration with Legacy Inventory System

Context:

  • Modern e-commerce platform needs to integrate with 20-year-old inventory system
  • Legacy system uses SOAP XML API with complex structure
  • Domain model uses clean, DDD-style design

ACL Implementation:

// Domain interface
interface InventoryService {
    boolean checkAvailability(ProductId productId, Quantity quantity);
    void reserveStock(OrderId orderId, ProductId productId, Quantity quantity);
    void confirmUsage(OrderId orderId);
    void releaseReservation(OrderId orderId);
}

// ACL implementation
class LegacyInventoryAdapter implements InventoryService {
    private final LegacySoapClient soapClient;
    private final XmlMapper xmlMapper;
    private final InventoryTranslator translator;
    
    @Override
    public boolean checkAvailability(ProductId productId, Quantity quantity) {
        // Create SOAP request
        String request = xmlMapper.createAvailabilityRequest(
            productId.toString(), 
            quantity.getValue()
        );
        
        // Call legacy system
        String response = soapClient.send("CheckAvailability", request);
        
        // Parse and translate response
        AvailabilityResponse availabilityResponse = xmlMapper.parseAvailabilityResponse(response);
        
        return translator.isAvailable(availabilityResponse, quantity);
    }
    
    // Other methods similarly implemented...
}

Key Takeaways:

  • Domain interface aligns with domain language
  • SOAP/XML complexities entirely hidden from domain
  • Translation clear and explicit

Example 2: Multi-System Customer Data Integration

Context:

  • Customer data spread across CRM, ERP, and legacy customer database
  • Each system has different customer model and access patterns
  • Need unified customer view in domain model

ACL Implementation:

// Domain interface
interface CustomerRepository {
    Customer findById(CustomerId id);
    void save(Customer customer);
}

// ACL implementation
class CompositeCustomerRepository implements CustomerRepository {
    private final CrmGateway crmGateway;
    private final ErpGateway erpGateway;
    private final LegacyDbGateway legacyDbGateway;
    private final CustomerAggregator aggregator;
    
    @Override
    public Customer findById(CustomerId id) {
        // Get data from all systems
        CrmCustomerData crmData = crmGateway.getCustomer(id);
        ErpCustomerData erpData = erpGateway.getCustomer(id);
        LegacyCustomerData legacyData = legacyDbGateway.getCustomer(id);
        
        // Aggregate and resolve conflicts
        return aggregator.createCustomer(crmData, erpData, legacyData);
    }
    
    @Override
    public void save(Customer customer) {
        // Break down into system-specific updates
        CrmCustomerData crmData = aggregator.extractCrmData(customer);
        ErpCustomerData erpData = aggregator.extractErpData(customer);
        LegacyCustomerData legacyData = aggregator.extractLegacyData(customer);
        
        // Update each system
        crmGateway.updateCustomer(crmData);
        erpGateway.updateCustomer(erpData);
        legacyDbGateway.updateCustomer(legacyData);
    }
}

Key Takeaways:

  • Domain sees unified customer model
  • Aggregation and conflict resolution hidden from domain
  • Each external system has dedicated gateway

Resources for Further Learning

Books and Publications

  • “Domain-Driven Design” by Eric Evans (original ACL concept)
  • “Implementing Domain-Driven Design” by Vaughn Vernon
  • “Patterns of Enterprise Application Architecture” by Martin Fowler
  • “Building Microservices” by Sam Newman
  • “Clean Architecture” by Robert C. Martin

Online Resources

  • Martin Fowler’s blog articles on integration patterns
  • Microsoft’s cloud design patterns documentation
  • DDD Community patterns repository
  • ThoughtWorks Technology Radar for integration approaches
  • Enterprise Integration Patterns website

Tools and Technologies

  • API Gateways: Kong, Ambassador, AWS API Gateway
  • Service Meshes: Istio, Linkerd, Consul
  • Integration Frameworks: Spring Integration, Apache Camel
  • Mapping Libraries: MapStruct, AutoMapper
  • Message Brokers: Kafka, RabbitMQ, ActiveMQ

This cheat sheet aims to provide a comprehensive overview of the Anti-Corruption Layer pattern. While ACLs add complexity, they are invaluable for protecting your domain model when integrating with systems that have different conceptual models or design philosophies.

Scroll to Top