Sign in
TodayMapLearnPracticeReview
Library
13 MINadvancedDesign PatternsNot started

Anti-Corruption Layer

An Anti-Corruption Layer (ACL) is a translation boundary between two bounded contexts — typically a new system and a legacy one — that prevents the legacy's domain model, terminology, and quirks from leaking into the new system. The ACL translates in both directions: outbound calls from the new system become legacy-compatible calls, and inbound responses from the legacy become clean new-domain objects. The new system never sees the legacy's data shapes, error conventions, or technical debt — it sees only its own clean model. The ACL is the price you pay to keep a migration from corrupting the system you're migrating to.

Why this matters

When you migrate from a legacy system, the temptation is to let the new system speak the legacy's vocabulary — to call the same stored procedures, accept the same JSON shapes, return the same error codes. This works for a week and corrupts forever: the new system inherits all the legacy's bad design decisions, and you've rebuilt the same mess in a new language. The ACL breaks this by forcing a translation: the new system has its own clean model, and the ACL is the seam where translation happens. Once the legacy is retired, the ACL can be removed too — leaving a clean system, not a translated mess.

Prerequisites
  • Microservices
  • Strangler Fig
Related
  • Strangler Fig
  • Sidecar
Used in

Foundational.

Lesson

How it works

The Anti-Corruption Layer is a pattern from Domain-Driven Design (DDD), introduced by Eric Evans. The metaphor: you're building a new, clean system (a city). Next door is a legacy system (a polluted industrial site). You don't want the pollution leaking into your city, so you build a wall with a controlled checkpoint — everything that crosses the boundary is cleaned, filtered, translated. That checkpoint is the ACL.

Concretely, the ACL is a service (or a layer within a service) that sits between your new code and the legacy system:

  • The new system calls the ACL using its own clean domain model.
  • The ACL translates the call into the legacy's protocol, schema, and conventions.
  • The ACL calls the legacy.
  • The legacy responds in its own (often ugly) format.
  • The ACL translates the response back into the new system's clean model.
  • The new system receives a clean object, unaware of the legacy's existence.

The new system's code never imports legacy types, never names legacy fields, never inherits legacy's error conventions. It only sees its own model. The legacy's quirks — its PascalCase JSON, its cryptic error codes, its status enums like 'PARTL' and 'CNCL' — live and die inside the ACL.

This pattern is essential during Strangler Fig migrations: each new service that needs to call legacy wraps the calls in an ACL, so the new service is built on clean concepts from day one. When the legacy is eventually retired, the ACL is removed — and the new service is already clean.

An ACL translates more than just data shapes. It translates:

  • Data schemas: new system's Order.id (UUID) ↔ legacy's ord_id (CHAR(8)).
  • Status enums: new system's OrderStatus.PAID ↔ legacy's stts_cd = 'PD'. The new system has meaningful enum values; the legacy's cryptic codes live in the ACL.
  • Units and types: new system's totalCents (int) ↔ legacy's tot_amt (DECIMAL dollars). The ACL handles precision and rounding rules.
  • Protocols: new system calls via typed gRPC; legacy is a SOAP service or a stored procedure. The ACL speaks both.
  • Error conventions: new system throws typed exceptions; legacy returns retcode 0/1/2/3. The ACL maps.
  • Naming and vocabulary: new system uses the business's current language (‘customer’, ‘subscription’); legacy uses outdated terms (‘acct’, ‘svc_plan’). The ACL renames.
  • Business rules: legacy may require a side-effect (writing to an audit table) that the new system shouldn't have to know about. The ACL hides it.
  • Pagination, filtering, sorting conventions: new system uses standard ?limit=10&offset=20; legacy uses a custom ?pg=2&sz=10 or no pagination at all.

The ACL is also a place to enforce invariants the legacy doesn't: validation, idempotency keys, rate limiting, observability. The new system gets a clean, well-behaved API; the legacy is left alone.

ACL vs Facade vs Adapter

These patterns are related but distinct. A Facade simplifies access to a complex subsystem — it hides complexity but doesn't necessarily translate domains. An Adapter makes one interface compatible with another at the API level. An Anti-Corruption Layer is a Facade + Adapter with a strategic intent: prevent the legacy's domain model from corrupting the new system's domain model. The difference is intent and scope. The ACL exists because the legacy's model is bad, not just complex; the goal is to keep the new system clean, not just to call legacy more conveniently.

Where does the ACL live?

  • As a separate service — a dedicated microservice that exposes a clean API and calls legacy. Other services call it, never the legacy. Easiest to reason about; some operational overhead.
  • As a library within the new service — the new service imports a legacy-adapter module that handles translation. Lower overhead; couples the new service to the adapter's release cycle.
  • As a sidecar — the ACL runs in a sidecar container alongside the new service. Useful if the ACL needs its own language (e.g., it must call SOAP and the new service is gRPC-native).
  • As a layer in a façade / API gateway — if the gateway supports transformation logic (e.g., Kong plugins, API Gateway mapping templates).

Implementation tips:

  • The ACL is its own bounded context. It owns the translation logic and the legacy's quirks. Don't spread translation across services.
  • Test the ACL with contract tests on both sides. The legacy side is fixed (you can't change it); the new side is your contract with new services.
  • The ACL is throwaway. When legacy is retired, delete the ACL. Don't build anything into the ACL you'd want to keep — that goes in the new system.
  • Keep the ACL thin. Don't put business logic in it; that belongs in the new system. The ACL translates, it doesn't decide.
  • Make the ACL observable. Translation bugs surface as ‘why did this call fail?’ — log both the clean request and the legacy request, with correlation IDs.

A common failure: the ACL becomes a god-object that knows too much. Resist this. The ACL is a translator, not a business layer.

Check yourself
interview

You're migrating from a legacy SOAP-based billing system to a new gRPC-based microservice. What does an Anti-Corruption Layer add beyond a simple protocol adapter?

Pick one answer.

Check yourself
interview

When the legacy system is finally retired, what happens to the Anti-Corruption Layer?

Pick one answer.

Engineering mental model

Mental model. Think of Anti-Corruption Layer as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does Anti-Corruption Layer mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”

Design lens

Before choosing Anti-Corruption Layer, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.

Original NO CAP systems visual for Anti-Corruption Layer.
Image unavailable. Original NO CAP systems visual for Anti-Corruption Layer.
Anti-Corruption Layer: a compact system-thinking visual.— Original NO CAP visual.
// Pseudocode
request = receive()
result = anti_corruption_layer(request)
return result

// Production questions:
// 1. What happens on timeout?
// 2. Can this operation be retried safely?
// 3. What is the bottleneck?
A minimal engineering sketch for reasoning about Anti-Corruption Layer.

Back-of-the-envelope reasoning

Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.

Interactive sandboxdeterministic

Interactive thought experiment: Anti-Corruption Layer

Change the variables below and predict what breaks first in Anti-Corruption Layer. The production lab can later reuse these same inputs.

System pressure6%
Try this

Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.

Hint

If you are stuck on Anti-Corruption Layer, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.

Check yourself
solid

You increase traffic by 10× in a system using Anti-Corruption Layer. What should you inspect first?

Pick one answer.

Check yourself
interview

Which statement is the safest engineering habit when using Anti-Corruption Layer?

Pick one answer.

Try this
interview

You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.

Production scenario: your system uses Anti-Corruption Layer, traffic suddenly spikes, and p99 latency doubles. What is your first move?

Interview drill

Answer this without notes: When would you choose Anti-Corruption Layer, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.

Engineering lens

A useful engineering lens for Anti-Corruption Layer: define the problem it solves, the simpler design that fails first, the constraint that forces you to introduce this concept, and the new failure modes the concept creates.

Numerical sanity check

Back-of-the-envelope reasoning beats fake precision. State your traffic, payload, concurrency and growth assumptions explicitly, then calculate enough to know whether the current architecture is orders of magnitude away from the target.

Check yourself
interview

Do not optimize for a memorized definition. Reason from the workload and failure mode.

Imagine the simplest version of a system using Anti-Corruption Layer. What breaks first as traffic grows by 10×, and what would you change before reaching 100×?

Pick one answer.

Trade-offs

What you gain, what you pay

Pros
  • +New system's domain model stays clean — no pollution from legacy's design decisions.
  • +Translation logic is centralized — one place to fix legacy quirks.
  • +New services can be built using current business vocabulary, not legacy's outdated terms.
  • +ACL is removable — when legacy retires, the new system is already clean.
  • +Decouples new system's evolution from legacy's stability.
Cons
  • −Adds a translation layer — extra latency, extra code, extra service to operate.
  • −Translation logic can be complex — legacy's quirks don't always map cleanly.
  • −Two systems to maintain during migration — the ACL is yet another moving part.
  • −Risk of the ACL becoming a god-object with business logic mixed in.
  • −Schema evolution on both sides means the ACL must be kept in sync with both.
Failure modes

How this breaks in production

  • ACL becomes a god-object — translation logic creeps into business logic; the ACL becomes hard to remove.
  • Translation bugs — subtle mismatches between legacy and new model (precision loss, status enum drift).
  • ACL performance bottleneck — extra hop on every legacy call.
  • ACL failure cascades — if ACL is down, new system can't reach legacy at all (need fallbacks).
  • Stale translations — legacy changes a field meaning; the ACL's translation is now wrong, silently.
  • ACL becomes permanent — team never retires legacy, ACL stays forever as tech debt.
Common mistakes

Don't fall into these traps

  • •Skipping the ACL ‘just for one call’ — that one call's legacy types propagate everywhere.
  • •Putting business logic in the ACL — it should translate only; decisions belong in the new system.
  • •Not testing both sides of the ACL — legacy-side contract tests catch translation drift.
  • •Letting the new system see legacy types even partially — leaks defeat the purpose.
  • •Treating the ACL as permanent infrastructure — it's scaffolding; plan to remove it.
  • •Not making the ACL observable — translation bugs need to be debuggable with correlation IDs.
Where you see it

Real systems using this

Every Strangler Fig migration that involves a domain model mismatch.Domain-Driven Design — the ACL is the canonical DDD pattern for context mapping.Migrations from vendor systems (SAP, Oracle, Salesforce) to in-house services.Replacing SOAP-based legacy services with REST/gRPC-based new services.Integrating with acquired companies' systems during M&A.
Teardowns

How real systems implement this

  • Domain-Driven Design context mapping (Eric Evans) — Evans introduced the ACL in the DDD 'Context Mapping' chapter as the pattern for managing relationships between bounded contexts where one is a legacy or third-party system whose model you don't want to adopt.
  • Migrations from mainframe/SAP to cloud microservices — Companies migrating from SAP or mainframe systems to cloud-native microservices universally build ACLs — the legacy's data model, error conventions, and vocabulary are deeply embedded and cannot be allowed to leak into new services.
  • Strapi / API gateways with mapping templates — API gateways like Kong or AWS API Gateway can implement ACL behavior via mapping templates and plugins — translating between an external messy API and the internal clean model. This is a lightweight, gateway-resident ACL.
Interview prompts

Practice saying it out loud

  • Q1What is an Anti-Corruption Layer, and how does it differ from a protocol adapter or a facade?
  • Q2You're migrating from a legacy billing system with a messy schema. How do you keep the new system clean?
  • Q3When does an ACL not make sense? When is the overhead not worth it?
  • Q4What happens to the ACL when the legacy system is retired? Why?
  • Q5How would you test an Anti-Corruption Layer? What contracts matter on each side?
Research

Further reading & references

System Design Primer
Open source
ByteByteGo — Scale from zero to millions
ByteByteGo
System Design Tutorial
GeeksforGeeks
System Design Roadmap
roadmap.sh
Design Patterns reference
Reference
Design Patterns reference
Reference
Design Patterns reference
Reference

Core explanations are original NO CAP material. External references are provided for deeper study and standards.

What next?

Mark as understood once the mental model clicks.

Next recommended

Strangler Fig