Anti-Corruption Layer (ACL)
Architectural pattern that isolates a system from external inconsistencies by translating incompatible models and protocols.
Updated on March 30, 2026
The Anti-Corruption Layer (ACL) is a strategic Domain-Driven Design pattern that creates a translation layer between your business domain and external legacy or third-party systems. It preserves the integrity of your domain model by preventing contamination from inappropriate concepts, data structures, or logic originating from other systems.
Fundamentals
- Domain model isolation: shields your bounded context from unsuitable abstractions of external systems
- Bidirectional translation: converts outgoing requests and incoming responses according to each system's needs
- Architectural decoupling: enables independent evolution of your domain without being constrained by legacy systems
- Single responsibility: centralizes all adaptation logic in a dedicated, testable layer
Benefits
- Domain model purity preservation without technical compromises imposed by external systems
- Progressive migration facilitation by allowing old and new systems to coexist
- Technical debt reduction by preventing obsolete models from propagating into new code
- Improved testability by isolating external dependencies behind a controlled interface
- Increased flexibility to replace or modify external systems without impacting the business core
Practical Example
Consider a modern e-commerce application that must integrate a legacy ERP system using numeric product codes, while the new domain uses UUID identifiers and a different categorization structure:
// Modern domain model (our system)
interface Product {
id: string; // UUID
name: string;
category: ProductCategory;
pricing: Money;
}
interface ProductCategory {
slug: string;
hierarchy: string[];
}
// Legacy ERP system model
interface LegacyProductDTO {
product_code: number;
product_name: string;
cat_id: number;
price_cents: number;
currency_code: string;
}
// Anti-Corruption Layer
class ERPProductAdapter {
private categoryMapping: Map<number, ProductCategory>;
constructor(private erpClient: ERPClient) {
this.categoryMapping = this.initializeCategoryMapping();
}
async fetchProduct(productId: string): Promise<Product> {
// Convert UUID -> legacy numeric code
const legacyCode = this.convertToLegacyCode(productId);
// Call legacy system
const legacyProduct = await this.erpClient.getProduct(legacyCode);
// Translate to our domain model
return this.toDomainModel(legacyProduct);
}
private toDomainModel(legacy: LegacyProductDTO): Product {
return {
id: this.convertToUUID(legacy.product_code),
name: legacy.product_name,
category: this.categoryMapping.get(legacy.cat_id) ?? this.defaultCategory(),
pricing: {
amount: legacy.price_cents / 100,
currency: legacy.currency_code
}
};
}
async saveProduct(product: Product): Promise<void> {
// Translate our model to legacy format
const legacyDTO = this.toLegacyDTO(product);
await this.erpClient.updateProduct(legacyDTO);
}
private toLegacyDTO(product: Product): LegacyProductDTO {
return {
product_code: this.convertToLegacyCode(product.id),
product_name: product.name,
cat_id: this.findLegacyCategoryId(product.category),
price_cents: Math.round(product.pricing.amount * 100),
currency_code: product.pricing.currency
};
}
}Implementation
- Identify boundaries: map bounded contexts and locate external systems that threaten your model's integrity
- Define domain interface: create pure abstractions reflecting business needs without external technical considerations
- Create adapters: develop translation components that convert between your model and the external system's model
- Implement mapping: establish transformation rules for data, identifiers, and business concepts
- Handle inconsistencies: define strategies for imperfect translations (defaults, enrichment, validation)
- Test exhaustively: verify bidirectional conversions and edge cases with unit and integration tests
- Monitor translations: instrument the ACL to detect anomalies and track integration performance
Architecture Advice
The Anti-Corruption Layer should not become a God Object. If translations become complex, decompose the ACL into specialized adapters (ProductAdapter, OrderAdapter, etc.) with clearly defined responsibilities. Use the Facade pattern to provide a unified interface when necessary.
Related Tools
- AutoMapper / Mapster: object-to-object mapping libraries to automate simple conversions
- NestJS Interceptors: mechanism to implement ACLs declaratively in NestJS applications
- GraphQL DataLoader: useful pattern for optimizing translations with batching and caching
- OpenAPI Generator: automatic generation of typed clients for external APIs to encapsulate
- Wiremock / MSW: mocking tools to test ACL without depending on external systems
The Anti-Corruption Layer represents a strategic investment that protects your most valuable software asset: a coherent and expressive domain model. By accepting translation complexity in a dedicated layer, you preserve the simplicity and maintainability of your business core, facilitating long-term evolution and progressive migration of legacy systems.
Let's talk about your project
Need expert help on this topic?
Our team supports you from strategy to production. Let's chat 30 min about your project.

