intermediate 3 min answer

A team wraps a legacy billing system behind an anti-corruption layer so new services never see its data model. Two years later the layer is 14000 lines and three engineers understand it. What did the pattern buy and when is a thinner wrapper the better call?

anti-corruption layerdddcouplingtranslationmaintenance
Show the full answer Hide the answer

What is gained, concretely

The new services were written against a model that makes sense, and they stayed that way. Without the layer, the legacy system's vocabulary leaks: its null conventions, its status codes with historical meanings, its denormalised customer record, its assumption that an account has exactly one address. Those leak into every consumer, into their database schemas and their APIs, and once they have leaked they are no longer removable because external contracts now depend on them.

The second gain is substitutability: the day the legacy billing system is replaced, the translation changes in one place.

What is paid

  • Every semantic difference must be resolved by someone, in code. That is where 14,000 lines comes from, and most of it is not accidental complexity - it is the genuine difference between two models, written down for the first time.
  • A new team dependency. Three engineers who understand the layer become the path for every change to every consumer's view of billing.
  • Latency and operational surface: an extra hop, its own deployments, its own failure modes, and its own capacity.
  • Debugging across a translation, which is harder than either side alone, because the data in the log does not look like the data in either system.
  • Drift. The legacy system keeps changing. A translation that nobody re-validates becomes subtly wrong, and the failure is a silently incorrect value rather than an error.

When the cost becomes visible

Not at the start - the layer is cheap for the first two consumers and expensive at the tenth, when it has absorbed ten sets of requirements and become a de facto domain service nobody designed. The signal to watch is the ratio of translation logic to business logic inside it: once the layer contains rules that exist in neither system, it has stopped being a translator.

When not to build the layer at all

The pattern is Eric Evans's, from Domain-Driven Design (2003), and it was described for a boundary between two teams' models rather than as a default wrapper for anything old. With one consumer, a shared client library is the better call. It gives naming and convenience without a deployable, a 2–5 ms hop, or a team. The honest test is the number of independent consumers and the expected lifetime of the legacy system:

  • One or two consumers, legacy system staying for years: a library, kept deliberately thin.
  • Many consumers, legacy system to be replaced: the layer, and its cost is the price of not having the old model in ten codebases.
  • Legacy system being switched off within a year: neither. Accept the leak, contain it in one module per consumer, and spend the engineering on the replacement.

How to keep it from growing

Rule: the layer may translate, and may not decide. No business rules, no new validation, no enrichment from a third source. A contract test per consumer, and a periodic re-validation against the legacy system's actual behaviour, keeps the drift honest. And write down, when it is created, the condition under which it will be deleted - usually the legacy system's retirement - because a translation layer with no end date becomes permanent architecture by default.