pattern

Anti-Corruption Layer

also called ACL

A translation boundary that keeps another system's model — usually a legacy or vendor one — from leaking into your domain.

dddintegrationlegacyboundariestranslation

Definition

When integrating with a system whose model you do not control and would not have chosen, an anti-corruption layer sits between the two and translates. Your domain speaks its own language; the ACL converts to and from the foreign one. Nothing outside the ACL knows the foreign model exists.

Why it matters

Without it, the foreign model wins. Its field names, its status codes, its peculiar nulls, its notion of what a customer is, spread outward through your codebase because it is always easier to pass the object through than to translate it. Within a year your domain is a thin veneer over someone else's, and you cannot replace them.

This is especially acute with vendor systems and mainframes, where the model reflects decisions made decades ago under constraints that no longer exist — a customer identifier that is an 8-character packed field, a status enum with eleven values of which three are used, a date that is sometimes a string and sometimes zero.

Implementation patterns

  • Translate at the edge, in one place. A single module owns the mapping. If foreign types appear in three packages, you no longer have an ACL.
  • Own the target model. The ACL maps to your domain, not to a convenient near-copy of theirs. A mapping that is one-to-one everywhere is a sign the corruption already happened.
  • Translate errors as well as data. Foreign error codes must become your domain's failure concepts, or the leak just moves to the exception handler.
  • Make the layer testable without the foreign system. Contract tests against recorded fixtures, so the mapping can be verified in CI.
  • Log both sides during rollout. The translation is where subtle mismatches hide, and they surface as business anomalies rather than errors.

Industry parallel

Any organisation running a core banking, insurance policy, or mainframe order system alongside modern services ends up with this layer, whether they name it or not. The ones that name it and place it deliberately can eventually replace the core, because only one module knows its shape. The ones that let COBOL copybook semantics diffuse into their microservices discover that "replacing the mainframe" means touching two hundred services, which is how a five-year modernisation programme becomes a ten-year one.

The pattern's value is therefore optionality, and it is one of the few abstractions where the option is very likely to be exercised, because legacy cores are eventually replaced.

Failure scenarios

  • The ACL that grows business logic. It starts translating, then starts deciding, and becomes a third system nobody owns.
  • Partial adoption. Most access goes through the layer, one urgent path bypassed it, and now the foreign model is in your domain anyway.
  • Mapping performance ignored. A translation that does an N+1 lookup against the legacy system per record.
  • Confusing an ACL with an API gateway. A gateway routes and secures; an ACL translates meaning. A pass-through gateway with a rename is not an ACL.

Trade-offs

You gain a domain you control and the ability to replace the foreign system. You pay in an extra hop, mapping code that must be maintained as both models evolve, and occasional loss of fidelity — some foreign concepts have no clean equivalent, and deciding what to do with them is real design work rather than plumbing.

If the foreign system is genuinely permanent, small, and its model is fine, the layer may not be worth it. That is a rarer situation than it appears.

Interview question

"You must integrate with a vendor CRM whose model contradicts yours in three important ways. Where does the translation live, and how do you stop the vendor's concepts leaking past it?"