intermediate 2 min answer

A new service must integrate with a legacy system whose data model is entangled and poorly named. What does an anti-corruption layer actually do, and how do you know it is working?

anti-corruption-layerdddlegacyintegrationboundaries
Show the full answer Hide the answer

What it does

An anti-corruption layer is a translation boundary that converts the legacy system's model into the new system's model, so that the legacy concepts do not propagate into the new code.

The problem it prevents is specific and consequential: a legacy model is not merely ugly, it encodes assumptions. A customer_type field with eleven values that mean different things depending on an unrelated flag; a status field where two values are historical and unreachable; a table that represents three different concepts distinguished by a discriminator. Importing that model imports the assumptions, and the new system inherits constraints it has no reason to carry.

Without the layer, the legacy model spreads through the new system by ordinary refactoring, and after a year the new service cannot be understood without understanding the old one — which defeats the purpose of having built it.

What it contains

  • Translation between models, in both directions.
  • Interpretation of encoded meaning: the legacy flags decoded into explicit concepts the new model names properly.
  • Handling of the legacy system's failure modes — its timeouts, its error conventions, its habit of returning success with an error string in a field.
  • Compensation for missing data, with defaults chosen deliberately and documented.
  • The legacy system's quirks, isolated: the ordering requirement, the field that must be sent even though it is ignored, the call that must precede another. All of it in one place, named, rather than distributed through the new codebase as unexplained conditionals.

How to know it is working

  • No legacy concept appears in the new system's domain model. Search for the legacy field names and system name in the core code; any hit outside the layer is a leak.
  • The new system's tests do not reference legacy structures, and its domain logic is testable without any legacy fixture.
  • Replacing the legacy system means rewriting the layer and nothing elsethe single best test of the boundary, and the reason the layer exists.
  • New engineers can work on the domain without knowing the legacy system exists.

The costs, honestly

  • It is genuine extra code performing no business function, and it will be questioned.
  • A performance and latency cost from the translation, usually small.
  • Two models to hold in mind when debugging an integration issue, and the mapping between them.
  • It can leak. Under deadline pressure someone passes a legacy structure through rather than translating it, and the boundary erodes exactly as a module boundary does. The defence is the same: an automated check that the legacy types do not appear outside the layer.

When it is not worth it

When the legacy model is genuinely good, which happens, or when the integration is small and read-only and the concepts map cleanly. A translation layer over an already-clean model is ceremony.

And when the legacy system is being decommissioned within months, where the layer's cost exceeds its remaining lifetime — though this judgement is frequently wrong, because legacy systems outlive their decommissioning dates with great reliability.