Anti-Corruption Layer (ACL)
How to protect your domain from contamination by external, legacy, or third-party systems through a translation layer that isolates incompatible models.
What problem it solves
When your system needs to integrate with an external system (legacy, third-party, or from another team), the data models and business concepts are usually very different. Without protection, the external systemβs model contaminates your domain:
Sin ACL:
Tu Servicio de Pedidos
ββββββββββββββββββββββββββββββββββββ
β createOrder(legacyCustomerCode, β
β legacyProductSKU, β β Conceptos del sistema legacy
β legacyPriceFormat, β infiltrados en tu dominio
β legacyTaxCalculation) β
ββββββββββββββββββββββββββββββββββββ
β
βΌ
Sistema Legacy ERP (modelo incompatible)
The concrete problems are:
- Model contamination: Your domain adopts names, structures, and rules from the external system
- Tight coupling: Changes in the external system require changes in your domain
- Growing complexity: Your code mixes business logic with translation logic
- Hard to test: You cannot test your domain without simulating the external system
How it works
The Anti-Corruption Layer is a translation layer that sits between your system and the external system. It translates concepts, formats, and protocols in both directions, keeping your domain clean.
Con ACL:
Tu Servicio de Pedidos
ββββββββββββββββββββββββββββββββ
β createOrder(customerId, β
β productId, β β Tu modelo de dominio limpio
β price, β
β tax) β
ββββββββββββ¬ββββββββββββββββββββ
β
ββββββββββββΌββββββββββββββββββββ
β Anti-Corruption Layer β
β β
β customerId β legacyCustCode β
β productId β legacySKU β β TraducciΓ³n bidireccional
β price β legacyPriceFormat β
β tax β legacyTaxCalc β
ββββββββββββ¬ββββββββββββββββββββ
β
βΌ
Sistema Legacy ERP
ACL components
| Component | Responsibility | Example |
|---|---|---|
| Translator | Converts data between models | LegacyCustomer β Customer |
| Adapter | Adapts the external systemβs interface | REST β SOAP, JSON β XML |
| Facade | Simplifies the external systemβs API | 5 legacy calls β 1 clean method |
| Mapper | Maps fields and values between models | status: "A" β status: "active" |
Translation example
Tu modelo (dominio limpio):
Order {
id: "ORD-001"
customer: { id: "C-123", name: "MarΓa GarcΓa" }
items: [{ product: "Laptop", quantity: 1, price: 1500 }]
status: "confirmed"
}
Modelo del sistema legacy:
LEGACY_ORDER {
ORDER_NUM: "00000001"
CUST_CODE: "CUST00123"
CUST_NM: "GARCIA, MARIA"
LINE_ITEMS: "LAPTOP|1|150000" (precio en centavos, pipe-separated)
STAT_CD: "CF"
}
El ACL traduce entre ambos modelos:
OrderACLTranslator.toExternal(order) β LEGACY_ORDER
OrderACLTranslator.fromExternal(legacyOrder) β Order
Placement of the ACL
The ACL can be implemented at different levels:
As an independent service:
Tu Servicio βββΊ ACL Service βββΊ Sistema Externo
As a module within your service:
Tu Servicio
βββ Domain Layer (limpio)
βββ Application Layer
βββ ACL Module βββΊ Sistema Externo
As part of the API Gateway:
API Gateway (con ACL) βββΊ Sistema Legacy
Advantages
- Clean domain: Your business model is not contaminated with external concepts
- Decoupling: Changes in the external system are absorbed in the ACL, not in your domain
- Isolated testing: You can test your domain without the external system (mock the ACL)
- Gradual migration: You can replace the external system without changing your domain
- Clarity: Translation logic is centralized and explicit
- Multiple integrations: You can have different ACLs for different external systems
Trade-offs / Disadvantages
- Additional code: The ACL is code that must be written, tested, and maintained
- Latency: One more processing layer on each call
- Change synchronization: When the external system changes its API, the ACL must be updated
- Mapping complexity: Some mappings are complex (data that has no direct equivalent)
- Risk of a bloated ACL: If the ACL grows too much, it can become a problem in itself
- Double maintenance: Changes in your model or in the external one require updating the ACL
When to use it
- Integration with legacy systems that have incompatible data models
- Consuming third-party APIs with models different from your domain
- Gradual migration from a monolith to microservices
- When multiple bounded contexts need to communicate without contaminating their models
- Integration with systems that use different protocols or formats (SOAP, XML, flat files)
- When the external system changes frequently and you want to isolate the impact
When to avoid it
- Integration with systems that share the same domain model
- Simple external APIs where translation is trivial (a mapping of 2-3 fields)
- Prototypes where development speed matters more than model purity
- When the external system is temporary and will be replaced soon
Common technologies and implementations
| Category | Options |
|---|---|
| Design patterns | Adapter, Facade, Translator, Mapper |
| Mapping libraries | MapStruct (Java), AutoMapper (.NET), class-transformer (TS) |
| Integration | Apache Camel, Spring Integration, MuleSoft |
| API Translation | GraphQL as an ACL, API Gateway with transformations |
Relationship with other patterns
- DDD (Bounded Context): The ACL protects the boundaries between bounded contexts
- API Gateway: Can implement ACL functions for external systems
- BFF: The BFF acts as an ACL between the frontend and the microservices
- Shared Kernel: An alternative to the ACL when two contexts deliberately share a common model
- Microservices: Each microservice can have its own ACL for external integrations
Next steps
The Anti-Corruption Layer is essential for maintaining the integrity of your domain. To understand how to handle failures in communication with external systems, explore the Retry pattern. To share models in a controlled way between contexts, review the Shared Kernel.