Skip to content
Richard Cooper
Go back

Anti-corruption layers

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:

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:

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.


Share this post:

Previous Post
One-way and two-way doors
Next Post
Bounded contexts and aggregates