In an earlier post I mentioned that the clean line you cut along when removing an external dependency “has a name: an anti-corruption layer,” and then promised to come back to it. So here we are.
It’s an unfortunate bit of naming — it sounds like compliance software — but it’s one of the most useful ideas I’ve stolen from Domain-Driven Design, and you don’t need the rest of DDD to get the benefit. The problem it solves is one every system runs into the moment it talks to something it doesn’t control.
How a foreign model creeps in
You integrate with a payment provider, a legacy platform, a credit-check bureau, another team’s service. You call their API, get their objects back, and — because it’s the path of least resistance — you start passing those objects straight through your code. Their field names. Their enums. Their idea of what a “customer” is, which is subtly not yours. Their null-where-you’d- expect-a-value quirks. Their occasional outright bugs that you end up coding around.
Do this for a year and their model has quietly colonised yours. Their concepts are now load- bearing in your domain. And the day you want to change provider — or they change their API on their schedule — the blast radius is your entire codebase, because their model is everywhere.
What an anti-corruption layer actually is
An anti-corruption layer (Eric Evans’ term) is a thin translation boundary between the outside model and yours. The rule is simple: your domain only ever speaks its own language. The foreign model stops at the layer; the layer translates it into your concepts on the way in, and yours into theirs on the way out. Their types never cross the line.
Concretely, that’s usually:
- An interface you own, expressed in your domain’s terms —
PaymentGateway.take(amount), notAcmePayClient. - A translator behind it that maps their request/response shapes to and from your own objects, and quietly absorbs their quirks.
- Your own data structures on your side of the boundary, so nothing downstream ever imports a type from their SDK.
It’s the depend-on-interfaces idea, but with the translation made explicit and given a place to live.
Why it’s worth the bother
I leaned on exactly this when I said I’d “changed my payment vendor three times at one company, and watched a finance provider go from external to in-house and back out again.” Those moves were survivable for one reason: the rest of the system never knew who the provider was. It talked to our interface; only the layer behind it knew the vendor’s name. Swapping providers meant writing a new translator, not chasing a foreign type through every corner of the code.
The payoffs, in order of how often they’ve saved me:
- Replaceability. The vendor you’re certain you’ll never change is the one that moves. The layer is what makes the move a contained piece of work instead of a quarter’s archaeology.
- A clean domain. Your core logic reads in your language, not a mash-up of three vendors’ vocabularies.
- Resilience to their changes. When they version their API or quietly change a field, the break lands in one translator, not in fifty call sites.
- A honest seam for testing. You can fake the interface and test your domain without their sandbox being up.
When not to reach for one
It isn’t free, and it isn’t always worth it. The translation layer is real code that has to be written and maintained, and wrapping everything in ceremony is its own kind of mess. I skip it when the external model is small and stable, when the integration is genuinely throwaway, or when their model and mine happen to line up well enough that translating would just be copying.
The judgement call is roughly: how messy is their model, and how likely is it to change or be replaced? The messier and the more likely, the more an anti-corruption layer earns its keep. For anything core — payments, policy data, anything a regulator cares about — I almost always build one.
The one-line version
An anti-corruption layer is a small, deliberate cost paid up front so that someone else’s model — and someone else’s change — can’t reach into your system and hold it hostage. It’s the difference between “we’ll never change that vendor” being a risk and being a shrug.