In Domain-Driven Design, what is an Anti-Corruption Layer (ACL), and why would a team put one between their bounded context and an external or legacy system?
answer
- translation wall at the boundary
- isolates legacy/foreign quirks
- adapter + translator + facade
- owned by the downstream context
- temporary investment tied to the dependency
basics
~10 sAn Anti-Corruption Layer is a translation wall between your code and someone else's system, so their messy or different data model doesn't leak into and pollute your own model.
solid answer
~40 sAn ACL is a dedicated translation layer - adapters, facades, mappers - sitting at the boundary between your bounded context and an upstream system you don't control, such as a legacy application, a third-party API, or another team's service whose model clashes with yours. It converts the external representation into your own domain's ubiquitous language before anything crosses into your core, and translates back on the way out. You pay the cost of writing and maintaining translation code, but in exchange your domain model stays clean, expressive, and free to evolve independently of the upstream system's quirks, deprecated fields, or inconsistent naming.
go deeper
Can state that an ACL translates an external model into your own model and explain the immediate benefit - keeping your model clean - using an everyday analogy.
Can name the typical building blocks (adapter, translator/mapper, facade) and identify when a legacy or third-party integration genuinely warrants one versus a thin pass-through.
Weighs an ACL explicitly against Conformist and Customer-Supplier, discusses ownership and ongoing maintenance cost, and knows to place it inside the downstream context's boundary.
Frames building an ACL as a risk-management and investment decision across a portfolio of integrations, including when to shrink or retire one as an upstream system improves, gets replaced, or a partnership matures.
## Why a boundary needs a translation layer A **bounded context** is a boundary inside which one domain model and its vocabulary - its **ubiquitous language** - apply consistently and mean one single thing. The moment that context needs data or behavior from a system outside its boundary, a translation problem appears: the outside system almost never uses the same concepts, field names, or invariants your model does. An **Anti-Corruption Layer** is the pattern for handling that translation deliberately, instead of letting foreign concepts leak in unmanaged. ## The moving parts Mechanically it is a small cluster of components living inside the downstream (consuming) context: - an **adapter** that speaks the external system's protocol, whether that's a REST client, a SOAP client, or a database driver reading someone else's schema; - a **translator or mapper** that converts field-by-field and concept-by-concept between the external shape and your domain's shape, sometimes resolving genuine semantic mismatches such as the upstream system encoding a status as a raw integer while your model needs a rich enum with business meaning; - and often a **facade** that presents a clean, domain-shaped interface to the rest of your context, hiding the fact that an external integration exists behind it at all. Requests flowing outward get translated back into the external system's expected format on the way out, so the whole exchange looks, from inside your context, as if you were only ever talking to your own well-behaved model. ## Why the pattern exists This pattern exists because bounded contexts are only useful if the model inside them stays coherent. If you let a legacy system's inconsistent field names, a third-party vendor's ever-changing schema, or another team's clashing terminology flow directly into your domain objects, you don't really have a bounded context anymore - you have a context whose boundary has been breached, and every consumer of your model now has to understand and work around the foreign system's quirks too. The ACL exists to absorb that cost in one place, at the boundary, so it doesn't spread throughout your codebase. It is one of DDD's **strategic design patterns** precisely because the decision to build one is a team-level, architectural choice about where translation complexity should live, not a low-level implementation detail. ## The trade-off The trade-off is straightforward to state but easy to underestimate in practice. | Side | What you pay, what you get | |---|---| | **On the cost side** | you write and must continuously maintain translation code: every time the upstream system's contract changes, someone on your team has to update the mapper, and that work is pure overhead that produces no new domain functionality of its own | | **On the benefit side** | your core domain model stays expressive and stable, free to evolve on your own schedule, and testable in isolation because your domain logic never has to reason about the external system's oddities directly - a test can construct clean domain objects without touching the real legacy integration at all | The size of that trade-off scales with how far the two models diverge: an ACL protecting against a wildly different, poorly documented legacy mainframe earns its keep quickly, while an ACL built preemptively against a modern, well-designed, cooperative internal service is often wasted effort, since a lighter **Conformist** or **Customer-Supplier** relationship would achieve the same result far more cheaply. ## Failure modes Failure modes show up in a few recognizable ways. 1. **The most common is an incomplete or overly literal translation**: the mapper copies field names one-to-one instead of translating concepts, so subtle semantic differences - for example, the upstream system's 'cancelled' status actually covering both customer cancellations and system-initiated voids - slip through untranslated and cause bugs that are hard to trace back to the ACL. 2. **The opposite failure is over-engineering**: a team mirrors the upstream system's entire schema inside the ACL 'just in case,' turning what should be a thin boundary into a second full integration to maintain, with most of it never actually used by the domain. 3. **A third failure is letting the ACL mask upstream data-quality problems silently** - translating garbage into plausible-looking domain objects - which pushes the real bug discovery downstream into production incidents instead of surfacing it where it originates. And because the ACL sits on the hot path of every external call, a poorly designed one can introduce real latency or become a single point of failure that nobody budgeted for. ## A concrete scenario A concrete real-world scenario: a company migrating off a decades-old mainframe order-management system onto modern microservices, using the **strangler fig** migration approach, typically puts an ACL in front of every new service that still needs data from the mainframe during the transition. The mainframe might represent a customer record as a fixed-width flat file with cryptic field codes and no concept of a proper `Address` value object; the ACL parses that representation and produces a clean `Customer` aggregate with a well-formed `Address` the new service's domain logic can work with directly. As pieces of functionality get strangled away from the mainframe and reimplemented natively, the corresponding piece of the ACL shrinks and eventually gets deleted entirely - the ACL is explicitly a temporary, targeted investment tied to the lifetime of the legacy dependency it protects against, not a permanent architectural fixture.
- Where does the ACL code physically live - inside your bounded context or the upstream system's?It lives inside the downstream (consuming) context, owned and maintained by the team that needs the protection. The upstream system doesn't know or care about your model, so only your team has the motivation to keep the translation correct, and owning it on your side lets you change or replace it without needing anyone else's permission.
- If the upstream system is a modern, well-designed internal service built by a cooperative team, do you still need a full ACL?Usually not as heavy a one. If the two models are already conceptually close and the owning team is responsive to your needs, a lighter Conformist or Customer-Supplier relationship is cheaper and achieves nearly the same result. An ACL is a targeted investment that's justified when the upstream model is legacy, third-party, or actively divergent from yours - building a full one against a friendly, well-aligned service is often wasted translation code that adds maintenance burden for no real benefit.
- What's a cost of an ACL that teams commonly underestimate going in?Ongoing maintenance: every upstream contract change requires someone to update the translation layer, and a badly designed ACL can silently mask upstream data-quality problems, which then require dual triage when a downstream bug turns out to originate upstream. Teams also sometimes mirror far more of the upstream schema than the domain actually needs 'just in case,' which turns a thin boundary into a second integration surface to maintain indefinitely.
Like a customs checkpoint at a national border: goods crossing in are inspected and repackaged into the receiving country's own paperwork format, so foreign documentation and units never enter the domestic system directly.
saying these in an interview costs you the question
- Says an ACL just means 'a DTO' or 'a data class'
- Thinks the ACL only translates field types, not business concepts or semantics
- Places ACL ownership with the upstream team rather than the downstream team
- Conflates an ACL with a generic API gateway
- Claims building an ACL removes the need for any contract or integration testing
- Cannot explain why a company would ever remove an ACL later