// Domain-Driven Design · 04
Anti-Corruption Layer
Translate a foreign model at the border so it never leaks inside.
How it fits together
- Orders domain uses / calls CustomerDirectory (1 Find(42))
- ErpCustomerDirectory implements CustomerDirectory
- ErpCustomerDirectory uses / calls Legacy ERP (2 SOAP call)
- ErpCustomerDirectory uses / calls ErpCustomerTranslator (3 translate)
Spot it
- Vendor field names appear in domain classes
- CUST_STAT_CD == 7 checks scattered in code
- A vendor upgrade breaks unrelated features
It costs
Extra mapping code and tests to maintain, plus a layer that can hide slow remote calls.
In code
public enum CustomerStatus { Active, OnHold, Closed }public sealed record ErpCustomerDto(string CUST_NO, string CUSTNM, int CUST_STAT_CD);public sealed class ErpCustomerTranslator{ public Customer ToDomain(ErpCustomerDto erp) => new( new CustomerId(erp.CUST_NO.Trim()), erp.CUSTNM.Trim(), erp.CUST_STAT_CD switch { 1 => CustomerStatus.Active, 2 => CustomerStatus.OnHold, 7 => CustomerStatus.Closed, var code => throw new UnknownErpCodeException(nameof(erp.CUST_STAT_CD), code), });}public sealed class ErpCustomerDirectory(IErpSoapClient erp, ErpCustomerTranslator translator) : ICustomerDirectory{ public async Task<Customer?> FindAsync(CustomerId id, CancellationToken ct) { var dto = await erp.GetCustomerAsync(id.Value, ct); return dto is null ? null : translator.ToDomain(dto); }}Go deeperThe full explanation and what to ask for · 1 min readExpandCollapse
When your context must talk to a system whose model you do not control, conforming to it spreads its names, codes and quirks through your code. An anti-corruption layer sits on the downstream side of the boundary: it speaks the foreign model on the outside and yours on the inside.
Inside it you usually find a facade that simplifies the remote API, an adapter that implements a port your domain defines, and a translator that converts data in both directions. Unknown values should fail at the boundary instead of defaulting silently to something plausible.
It pairs well with the strangler fig: new code reaches the legacy system only through the layer, so the legacy can be replaced behind it. When NOT to: if the upstream model is good and stable, such as a published language you are happy with, conforming is cheaper.
Ask for it
- anti-corruption layer
- translator
- adapter
- facade
- port
- conformist (the alternative)
Add an anti-corruption layer between Orders and the legacy ERP: a CustomerDirectory port in the domain returning our Customer, an adapter calling the ERP SOAP API, and a translator mapping CUST_STAT_CD 1/2/7 to Active/OnHold/Closed that rejects unknown codes. No ERP types outside the adapter.