Resources · Patterns

The anti-corruption layer: keeping the old system's quirks out of the new one

In short

When a rebuilt workflow runs alongside a legacy application, copying the old data model is the tempting shortcut. Eric Evans's anti-corruption layer is a translation boundary that lets the new workflow mean what it says while the old system stays unchanged. Treat it as scaffolding where it faces the system you're retiring, keep it where it faces systems you don't control, and test every translation rule as a requirement.

Abstract illustration: two systems separated by a purple translation layer

You've decided against a big-bang rewrite. One workflow, say invoicing, is being rebuilt on a modern stack while the current system keeps running. Sooner or later the two have to exchange data, and the obvious question comes up: should the new workflow just use the old tables and field names?

It's tempting. The schema exists and the integration looks free. But every column you copy carries a decision someone made in 2011 for a reason nobody remembers. Copy enough of them and you have built the old system again, in a newer language.

The alternative is a translation boundary between the two. Eric Evans named it the anticorruption layer in his 2003 book on domain-driven design, and you don't need to know anything else about DDD to use it. This piece explains the idea on an invoicing example, then sets out when the layer is scaffolding, when it stays, and what skipping it costs.

What Evans meant by "anticorruption"

Every system has a model: the names it gives things, the states a record can be in, the rules it applies. When two systems talk, one model leaks into the other. If the other system is old and badly documented, what leaks is its quirks.

Evans put it this way in the DDD Reference: a large interface with an upstream system "can eventually overwhelm the intent of the downstream model altogether, causing it to be modified to resemble the other system's model in an ad hoc fashion." His remedy is an isolating layer that gives your system what it needs "in terms of your own domain model", talks to the other system through its existing interface, and translates between the two models in both directions.

In plain terms: the new system speaks its own language, the old one isn't touched, and the layer translates. Microsoft's Azure Architecture Center describes the same pattern as a facade between subsystems "that don't share the same semantics", built for exactly this kind of gradual migration.

A worked example: rate cards and unbillable lines

Take a legacy invoicing module at a services company. Consultants log time against allocations. Each allocation should point to a rate card, but the rate_card_id column has been nullable since the beginning. When it's empty, the code invoices the line at zero. Finance learned years ago that a zero line means "someone forgot the rate card", and fixes it by hand before the invoice goes out.

That is a business rule. Nobody wrote it down; it lives in a null check and in two people's habits. Copy the legacy table into the rebuilt workflow and you copy the rule exactly as it is: a nullable column and a zero that means something else.

The rebuilt workflow should say what it means instead. In the new model, an invoice line is either billable, with a rate attached, or unbillable, with a stated reason. The requirement reads: while an allocation has no rate card, the system shall flag the invoice line as unbillable rather than invoicing at zero. That sentence is also an acceptance test.

Now the two models disagree, and the anti-corruption layer is where the disagreement is settled.

Both are translation rules, not new business logic. That distinction is the whole discipline.

Why a parallel rebuild needs a boundary, not a mirror

When you replace a system in steps, with Fowler's strangler fig as the usual picture, old and new run side by side for months or years. Meanwhile the rebuilt workflow depends on the legacy app for whatever hasn't moved yet: customers, contracts, the ledger. The pressure to share a data model is strongest exactly then.

Mirroring ties the two systems together in three ways you will regret.

Evans warned that "the models of legacy systems are usually weak". A boundary lets the new workflow be valuable now and separable later.

What goes in the layer, and what doesn't

A good anti-corruption layer is boring. It contains:

It should not contain business rules or orchestration; Microsoft's guidance says the same. The moment the layer starts deciding whether a line is billable, rather than translating that a line is unbillable, the rule lives in two places. A useful test: every rule in the layer should read "when the old system says X, we mean Y". If it needs "unless" twice, it is business logic and belongs in the new workflow as a requirement.

Scaffolding or permanent fixture?

"So we're building something we'll throw away?" Often, yes. The Thoughtworks authors of Patterns of Legacy Displacement call this transitional architecture and are blunt about it: "you will have to invest in work that will be thrown away." Their metaphor is scaffolding: you pay for it, it keeps the work safe, and you are glad to see it go.

Which is yours? It depends on what sits on the other side.

Temporary, when the other side is the system you're retiring

If the layer faces the legacy app the new workflow replaces, it should disappear as that app does. The zero-amount write-back can be deleted the day the month-end report moves to the new data. The same authors call these pieces legacy mimics, and advise building them to be removed.

Permanent, when the other side isn't yours

If the layer sits between your new workflow and an ERP, a payment provider or a partner's API, it stays. You don't control that model and it will keep changing without asking you. The Legacy Mimic article draws exactly this line: the layer facing an external partner system "will endure", so it isn't counted as transitional. A legacy module that is years away on the renewal backlog sits in between: give that layer a named owner and a date to review whether it can go.

What skipping it costs

In our experience the cost doesn't arrive at build time. It arrives a year or two later, when the second workflow is being rebuilt and the team discovers the first one is welded to the legacy schema. On an insurance project, a new policy service had been built directly on the old policy tables to save a few weeks. When the time came to change how policies were versioned, both systems had to change together, with a freeze the business hadn't budgeted for. The weeks saved were repaid several times over.

The U.S. Government Accountability Office reported in 2023 that federal agencies typically spend about 80 percent of more than $100 billion a year in IT on operating and maintaining existing systems. A new system coupled to an old one joins that line of spend on day one.

There is a quieter cost too. Without a boundary there is no single place where "what the old system means" is written down; the translation rules scattered through the new code are the undocumented business logic you started with, relocated. With the layer, they are a list your team can read, question and test.

Where Fabrica stands

Fabrica's CLEAR method treats the boundary as part of the evidence. Capture records what the running system actually does, quirks included, and Lock turns each one into a requirement in the System Specification with a stable ID, approved by an owner before anything is built. The rebuilt workflow runs as a modern twin with isolated data, so the layer between twin and legacy is explicit, and Attest tests both implementations against the same specification, so the translation is proven rather than assumed. The current system keeps serving until the evidence supports a move, and the parts of the layer that face it can be removed workflow by workflow as it retires (see how it works).

Draw the line before the first commit

The anti-corruption layer is what lets a parallel rebuild stay parallel: the new workflow means what it says, the old system carries on unchanged, and the two can be separated later without doing the work again. Decide where the boundary sits, and who owns it, before the rebuild starts. A line drawn on day one is cheap; finding it afterwards is not.

Questions to ask before you build the layer:

Keep reading

Related

Which priority is your system holding up?

Tell us about it on a 30-minute call. We'll suggest a first scope and what a technical review would need to confirm.

Book a 30-minute call