Cloud Native Patternsintermediate7 min

Anti-Corruption Layer

Put a translation layer between your clean model and a messy legacy system, so the old system's quirks never leak into your new code.

Ever inherited a system where the customer's name lives in a field called CUST_NM, the street address in ADDR_LN1, dates are stored as eight-digit strings, and "on hold" really means a status code of "7"? Now imagine building a clean, modern service that has to talk to that thing every day. Without care, its weirdness seeps into your beautiful new code until your model looks just as crusty as the old one.

The Anti-Corruption Layer is the firewall against that seepage. It's a dedicated translation boundary that lets your new system stay clean while still cooperating with the messy old one.

The problem

When two systems with different models talk directly, the stronger model usually wins — and it's rarely the one you want. If your new service calls the legacy system and uses its data shapes directly, those shapes spread through your codebase: the cryptic field names, the magic numbers, the assumptions baked in decades ago.

This is coupling at its worst. Your new system becomes dependent on details it shouldn't know about, and you lose the ability to evolve independently. The moment the legacy system changes, your clean code breaks — and over time, the corruption is complete: there's no clean model left to protect.

Step through it below. An Orders service reads customers straight from a legacy CRM, and the CRM's field names spread through its modules. Then the CRM team renames a single field. Predict what breaks before you look, then flip the switch to replay the same rename with an anti-corruption layer in between.

How it works

An anti-corruption layer sits between the two systems as a dedicated translator. Your new service never speaks to the legacy system directly; it speaks to the ACL in its own clean vocabulary. The ACL then translates that request into whatever the legacy system expects, calls it, and translates the response back into your model on the way home.

Inside, the ACL is doing abstraction work: mapping CUST_NM to name, parsing those eight-digit dates, turning status "7" into a proper OnHold value. Your code stays blissfully unaware of all of it.

Step through one round trip below and watch each field change its name as it crosses the layer. When the status code arrives, predict what the ACL should hand back to Orders.

Tip

Keep the ugliness in one place. The whole point is that all the gnarly mapping logic lives inside the ACL and nowhere else. If translation rules start leaking into your domain code, the layer isn't doing its job — tighten the boundary.

Watch out

Translate meaning, not just names. The most common failure is a pass-through ACL: it renames CUST_NM to name but forwards status "7", legacy error codes and zero-padded IDs unchanged. Those values carry the old system's assumptions, so the new code soon starts checking status == "7" and the leak is back, one hop later. The layer should speak your domain's language in both directions, including its errors.

Check yourself

The legacy CRM starts returning dates as eight-digit strings like 20240131 instead of ISO dates. Your Orders service sits behind an anti-corruption layer. Where should the fix go?

When to use it

An ACL shines during incremental migrations. You're carving a microservice out of a monolith, or replacing a legacy platform piece by piece, and the two must coexist for a while. The ACL quarantines the old system so the new one can grow cleanly — and when the legacy system finally retires, you delete the layer and your model is left pristine. It pairs well with the strangler fig approach to gradual replacement.

It's also the right tool whenever you integrate with an external third-party API whose model you don't control: wrap it in an ACL so vendor quirks never become your problem. The trade-off is a real one — it's another component to build, test, and maintain, and it adds a little latency per call. For two systems that already share a clean, stable model, an ACL is just overhead you don't need.

Check yourself

Your ACL renames every legacy field to a clean name, but passes the legacy status code through as status: "7". Orders now checks status == "7" in four places. What's wrong?

Key takeaways

  • An anti-corruption layer (ACL) is a translation boundary between a new system and a legacy or external one.
  • It converts requests and data between the two models so each side keeps its own clean design.
  • It stops a legacy system's quirks, bad names, and odd data shapes from leaking into your new code.
  • It's a cornerstone of incremental migration: the new system grows while the old one stays quarantined behind the layer.
  • The cost is an extra component to build and maintain, plus a little latency on each translated call.

Keep going