skip to content

When integrating a modern service with a legacy system (or an external system your team doesn't control) whose domain model is a poor fit for yours, what does an anti-corruption layer do, and what does it cost to maintain?

level: principalimportance: should knowfreq 45%

answer

  1. Eric Evans / DDD origin
  2. translation boundary at the edge
  3. Strangler Fig migration pairing
  4. corruption stays in the adapter
  5. targeted, not default

basics

~20 s

An anti-corruption layer is a translation wall between your system and someone else's messy or outdated one - it converts their concepts into yours at the boundary, so their quirks don't leak into your code. It costs extra code to build and keep updated.

solid answer

~50 s

An anti-corruption layer (ACL, from Domain-Driven Design) is a dedicated translation boundary — typically an adapter or facade module — placed between your bounded context and an external or legacy system whose model, terminology, or invariants don't match yours. It converts the external system's requests/responses/events into your domain's own model at the edge, so your core domain code never has to understand the legacy system's quirks or outdated concepts directly. This buys insulation: your domain model stays clean and can evolve independently, and when the legacy system eventually gets replaced, only the ACL changes, not your core logic. The cost is real: you're maintaining a genuine translation layer with its own logic, tests, and edge cases, and if the mapping is complex, the ACL itself becomes a nontrivial piece of software needing ownership. It's a targeted tool - appropriate at a genuinely messy boundary, not a default wrapper around every integration.

go deeper

for a junior

Can describe an ACL as 'a translator between two systems' at a high level, without needing migration strategy context.

for a middle

Recognizes when an integration's external model is messy enough to warrant a dedicated translation layer versus a thin pass-through wrapper.

for a senior

Designs the ACL's boundary and interface for a real integration, deciding what belongs in the adapter versus the domain, and tests the translation logic including legacy edge cases.

for a principal

Decides, at a platform/migration-strategy level, where ACLs are mandatory (e.g., every legacy-system integration during a Strangler Fig migration) versus wasteful overhead, and owns the long-term plan for eventually retiring the ACL alongside the legacy system it isolates.

## What an anti-corruption layer is An **anti-corruption layer (ACL)** is a Domain-Driven Design pattern, introduced by Eric Evans, for the specific situation where your system needs to integrate with another system — often legacy, a third-party product, or another team's bounded context — whose domain model, terminology, or data quality doesn't match yours, and where letting that mismatch leak directly into your codebase would degrade your own model over time. Mechanically, the ACL is a dedicated translation boundary: a module, adapter, or set of facade classes that sits between your code and the external system, whose entire job is converting between 'their' representation and 'yours' at every crossing. Nothing on your side of the ACL touches the external system's raw types, field names, status codes, or quirks directly — your domain code calls a clean interface expressed in your own ubiquitous language, and the ACL is the only place that knows both vocabularies. ## Why the pattern exists The reason this pattern exists is that integration boundaries are where model corruption happens by default. If you don't deliberately translate, the path of least resistance is for your code to use the external system's DTOs, enums, and field names directly wherever convenient — and once that starts, it spreads: - your domain logic ends up littered with conditionals for the legacy system's edge cases - your own domain concepts get bent to match the external system's shape instead of the shape actually correct for your problem - and eventually you can't tell which parts of your 'domain model' reflect your actual business rules versus accidental inheritance from someone else's legacy system The ACL draws a hard line: corruption is allowed to exist in the adapter code, but not to cross into the domain core. ## The trade-off The trade-off is genuine engineering cost versus long-term model integrity. Building an ACL means writing real translation logic — often including validation, defaulting for missing fields, reconciling inconsistent status vocabularies, and sometimes compensating for the external system's outright bugs — plus tests for that translation logic, and someone has to own it as its own piece of software with its own failure modes. For a small, clean, well-documented integration this is overkill: wrapping a well-designed external API's DTOs one-to-one with an ACL just to satisfy the pattern adds indirection with no real translation happening, which is wasted effort. The pattern earns its cost specifically when the external model is genuinely messy, unstable, or conceptually foreign to your domain — a legacy mainframe with decades of undocumented status-code meanings is the canonical case, but a third-party SaaS with a data model shaped around their product's history rather than your domain is another common one. ## The payoff over time The payoff shows up over time, not immediately: because your domain model never directly depends on the external system's shape, that system can change its API, get replaced entirely, or be migrated off gradually, and the blast radius is confined to the ACL. If you'd instead let the legacy model leak into your core, replacing that system becomes a project that touches your entire codebase, because your domain logic is riddled with assumptions baked in from a system you're trying to get rid of. This is especially valuable in a **Strangler Fig migration**, where a team incrementally replaces a legacy system: the ACL lets new code be written against a clean target model from day one, routing through the ACL to the old system for now and later re-pointing the same ACL interface to the new system, with zero changes needed in the domain code that consumes it. ## Failure modes on both sides of misuse Failure modes show up on both sides of misuse. | The misuse | What it produces | |---|---| | **Under-using the pattern** — skipping the ACL and wiring legacy DTOs straight into domain logic | produces the corruption described above: brittle domain code, conceptual confusion between what the business actually needs and what an old system happens to expose, and an expensive, risky legacy-replacement project down the line | | **Over-using it** — building an elaborate ACL for a clean, stable, well-modeled integration | produces unnecessary indirection and extra code to maintain, adding cognitive overhead without protecting anything meaningful | | **A subtler failure** — building an ACL that's too thin | translating field names but not concepts, so the legacy system's actual business assumptions still leak through the interface even though the syntax looks clean | ## A concrete real-world scenario A concrete real-world scenario: a retailer migrating off a 20-year-old mainframe order-management system builds a modern order service with its own clean Order aggregate (states: placed, fulfilled, cancelled, refunded). The mainframe exposes orders through a rigid legacy interface with seventeen numeric status codes, several of which map to the same modern concept and a few of which are dead codes nobody remembers the meaning of. The ACL is the one module that talks to the mainframe directly, translates those seventeen codes into the four clean states the domain actually needs, and absorbs every future oddity discovered in production — so when the retailer eventually decommissions the mainframe, only the ACL's implementation is replaced; the order domain logic, and everything built on top of it, never has to change.

  • How does an anti-corruption layer specifically support a Strangler Fig migration off a legacy system?
    New code is written against the ACL's clean target interface from the start, with the ACL initially routing calls through to the legacy system underneath. As pieces of the new system come online, the ACL's internals are re-pointed to the new implementation instead, with no changes required in any domain code that only ever depended on the stable ACL interface, letting the migration happen incrementally rather than as one risky cutover.
  • What's the risk of building an anti-corruption layer that only renames fields but doesn't translate the external system's underlying business assumptions?
    A thin, syntax-only ACL still lets the external system's actual behavior and invariants leak through — for example, an external system's ability to 'un-cancel' an order still shows up as a possible state transition even if the field names now match your domain vocabulary. Real translation has to reconcile the concepts and rules, not just the field labels, or the corruption just moves one layer deeper instead of being stopped.
  • When would building an anti-corruption layer be a wasted effort?
    When the external system's model is already clean, stable, and conceptually close to your own domain — in that case an ACL just adds an indirection layer that passes data through with no meaningful translation happening, at the cost of extra code and maintenance. The pattern earns its keep specifically at messy, unstable, or conceptually foreign boundaries, not as a default wrapper around every integration.

An anti-corruption layer is like a professional interpreter at a diplomatic meeting: the interpreter deals with the other side's idioms and awkward phrasing, and only translates a clean, accurate statement into your language - you never have to learn their dialect yourself.

saying these in an interview costs you the question

  • Applies an ACL to every integration by default regardless of how clean the external model is
  • Confuses an anti-corruption layer with a generic API gateway or reverse proxy
  • Builds a translation layer that only renames fields without addressing conceptual/behavioral mismatches
  • Doesn't connect the pattern to legacy migration or Strangler Fig strategies
  • Assumes the ACL is free to build and maintain
  • Lets domain code reference the external system's types directly 'just this once'

context