Anti-Corruption Layer (ACL)
Architectural pattern that isolates a system from external inconsistencies by translating incompatible models and protocols.
Updated on January 8, 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.
