Skip to main content

Strangler Fig & Anti-Corruption Layer

Editorial

Introduction

Every pattern in this course so far has quietly assumed a luxury: a blank page. You got to design the discovery, the edge, the mesh, the config pipeline. This lesson starts where most real engineering starts instead: with a system that already exists, already works, already makes the money, and is already hated.

You know this system. It is nine years old. The people who wrote its core left. Its tests are folklore, its documentation lies, and every team that touches it does so the way you would handle a wasp's nest. And Microservices — and When to Split has done its work: the signals say it is time to break it apart. The question this lesson answers is the one that decision leaves behind, and it is the harder one: how do you replace a system that cannot stop running? Every hour of every day, real customers are inside it. There is no maintenance window long enough. There is no second copy of the business to practice on.

Two patterns, working as a pair, are the honest answer. The strangler fig is the strategy: grow the new system around the old one, feeding on its traffic, until the old one is an empty shell you can delete. The anti-corruption layer is the discipline that keeps you sane while both are alive: a translation membrane that lets new code talk to old code without the old system's ghosts moving into the new walls.

One warning before we start: the villain of this lesson is not the legacy system. It is the instinct every engineer feels when they look at one.

Infographic titled: replace it while it runs. Four numbered stages sit side by side. Stage one, wrap: a facade lands in front of the untouched amber legacy block, one hundred percent legacy. Stage two, bite: the first slices cut over and a small green block of new services appears as the legacy shrinks. Stage three, shell: the legacy is a thin sliver and the green mass carries most traffic. Stage four, delete: a dashed outline marked deleted is all that remains of the legacy, and the green block, now labelled the fig, stands at zero percent legacy. A sky band notes that users are inside the whole time and every step is reversible, and the dark band states the rule: the old system is never off until the new one carries the load, never a gap in the canopy.

The Rewrite Graveyard

The instinct is the full rewrite. Freeze the old thing, build a clean one beside it with everything we know now, then switch. Every engineer who has ever read the legacy codebase has felt it. It is also one of the most reliably fatal decisions in software, and it fails for four reasons that compound.

The world does not pause. A rewrite of a large system takes years, and the business cannot stop asking for features while it happens. So either the old system keeps changing, and the rewrite chases a moving target, or it is frozen, and the company stops responding to its market. The canonical corpse is Netscape, which stopped shipping its browser for roughly three years to rewrite it from scratch, and watched its competitor take the market in the meantime.

The old system is the spec. Nobody knows everything the legacy does. Its behaviour is the accumulation of a decade of bug fixes, edge cases, and deals with customers nobody remembers making. The requirements document for the rewrite is the old code itself, and the old code is exactly the thing nobody can read. You will discover what the old system did the day the new one fails to do it.

Sunk cost compounds. Two years in, over budget, behind schedule, the rewrite is too big to cancel and not ready to ship. Projects in that state consume companies.

The cutover is one giant bet. However it goes, the plan ends with a night where you switch everything at once. Every mistake made in three years of rewriting is discovered by production traffic on the same day.

Notice what all four have in common: they are consequences of replacing everything at once. Which suggests the cure is not a better rewrite. It is refusing the "at once".

Two timelines compared. The big bang: three red tags, requirements drift, old code is the spec, sunk cost grows, hang over a long grey bar reading nothing ships for years, which ends at a red lightning cutover marked one bet, with the caption: years of mistakes, discovered in one day. The fig: a staircase of eight small green checked steps climbs steadily under the caption value ships the whole time, each step small, watched, reversible, no cliff anywhere. The dark band states the economics: be wrong in small steps, not in one giant bet.

The Fig: Wrap, Don't Replace

Martin Fowler found the alternative growing on trees. In the Queensland rainforest, a strangler fig starts as a seed dropped in the canopy of a host tree. It sends roots down the host's trunk, thickening year by year, until the fig is a structure that can stand on its own. Eventually the host dies and rots away, and what remains is the fig, standing in the exact shape of the tree it replaced. At no point was there a gap in the canopy.

As a migration strategy, that picture translates into one rule and three mechanisms.

The rule: the old system is never switched off until the new one is already carrying the load. You do not build the replacement beside the traffic and swap; you grow it inside the traffic, piece by piece, and the old system shrinks by attrition.

The mechanisms. First, find a seam: a boundary where you can cut without the patient noticing. Second, intercept: get between the callers and the old system, so requests can be redirected one slice at a time. Third, capture the assets: the data and state the old system owns must progressively become available to the new one, because a service without its data is a brochure.

And holding it all together is a species of code this course now names deliberately: transitional architecture. The routing shims, the sync bridges, the comparison rigs: scaffolding you build intending to delete it. Teams that fail at strangling usually fail here, in one of two mirror-image ways: they refuse to build scaffolding because it is "throwaway work", which makes every step a leap, or they build it and never take it down, which we will meet later as its own disease. The fig's economics are honest: the scaffolding costs real money, and it buys you the two things the rewrite could not offer at any price: the ability to ship value the whole time, and the ability to be wrong in small, reversible steps.

The Seam Is the Front Door

Where do you cut a live system? For a system serving requests, one seam already exists, and you built it three lessons ago: the front door. Every request already passes through one place, the gateway from API Gateways: The Front Door. A router that can inspect a request and choose a backend is a scalpel already resting against the seam.

The move: put the facade in front of the legacy system so that, on day one, it routes everything to the legacy, unchanged. You have altered nothing about behaviour; you have gained a steering wheel. From now on, migration is a routing decision: build the new catalog service, and the facade sends /catalog/* there while everything else flows to the monolith exactly as before. The monolith does not know, and does not need to know, that it just lost a limb. And if your legacy is not a request server at all, a batch job, an event consumer, the seam moves but the logic does not: intercept the queue it reads or the files it is fed, and redirect one input family at a time.

Three judgments make the difference between a controlled strangling and a mess.

Slice by capability, not by code. A slice must be a family of requests the facade can recognise: catalog, reports, checkout. Slicing by internal module or by database table gives you pieces a router cannot route.

Choose the first slice for learning, not for glory. The ideal first candidate changes often (so the payoff is real), touches little shared state (so asset capture is easy), and is not the crown jewels (so a mistake is survivable). Reports and read-only pages are classic first slices. Payments is a terrible one, not because the pattern cannot handle it, but because you will make your beginner mistakes somewhere, and it should not be there.

The dial must turn both ways, instantly. Routing 10% of catalog traffic to the new service is only safe if turning it back to 0% takes seconds. A cutover you cannot undo quickly is not a step, it is the same giant bet the rewrite made, just smaller. This is the same discipline the config lesson demanded of its pipeline: staged, watched, and reversible without a meeting.

Diagram titled: migration equals a routing decision. Live users enter a violet facade that routes by capability. Catalog flows on a green arrow to its new service at one hundred percent. Reports flows on an amber arrow to its new service at ten percent, with a dashed return arrow showing the dial turns back. Orders and payments flow unchanged on a dark arrow to the legacy box, where the catalog and reports rows are already dimmed and dashed. A green band carries the three judgments: slice by capability, start cheap, dial back in seconds. The dark band states the day-one move: everything routes to the legacy, and nothing changes.

Prove It Before You Cut

The routing dial gives you control. It does not give you confidence. Before the dial moves at all, you want evidence the new slice actually behaves like the old one, and there is a mechanism for exactly this, one you already met in Shadow Traffic & Dark Launches: run both, serve one, compare.

The facade duplicates each matching request: one copy to the legacy, whose answer is returned to the user as always, and one copy to the new service, whose answer is recorded and thrown away. A comparison rig diffs the two answers, and that diff is the most honest document your migration will ever produce, because of what this lesson already established: the old system is the spec. The diff is how you finally get to read it.

Which is why the mindset matters: a mismatch is a decision, not a bug report. Some mismatches are new-side bugs; fix them. But some are the old system's quirks: the rounding rule from 2014, the field that is uppercase for reasons lost to history, the off-by-one a customer's integration has depended on for years. For each one you must decide: reproduce the quirk faithfully, or fix it deliberately and tell the people it affects. What you may not do is find out about it from production after cutover.

One honest complication, and it is the deepest water in any strangling: state. While both systems are alive, they are reading and often writing overlapping data. Having the application write to both stores itself is the classic trap: the two writes cannot be made atomic, so a partial failure quietly forks the truth, the same poison Outbox + CDC: Reliable Events from Your Database taught you to respect. The CDC-shaped answer, one store remains the writer and changes flow outward from its log, is the safe backbone during migration. The full choreography of cutting data over without downtime is deep enough to own two lessons of its own, Zero-Downtime Data Migrations and Schema Migrations: Expand-Contract; what belongs to this lesson is the judgment: traffic cutover and data cutover are separate steps, and pretending they are one is how migrations lose data.

Diagram titled: run both, serve one, compare. The facade duplicates a request: a solid amber arrow carries it to the legacy, whose answer is served to the user, and a dashed green arrow carries the copy to the new service, whose answer is diffed. Both boxes drop short arrows into a diff panel showing one green match, total 84.00 equals 84.00, and one red mismatch, tax 6.72 versus 6.71, annotated as the 2014 rounding rule with the choice: reproduce it, or fix it. The dark band states the mindset: the old system is the spec, the diff is how you read it, and a mismatch is a decision, not a bug report.

The Drill: Strangle One Monolith

Theory ends here. There is a nine-year-old monolith on the bench with four capabilities inside it, a facade already in front, and live traffic flowing.

Migrate it. Build each new service, prove it in shadow, and move the dial when the evidence says so. One of the four capabilities is hiding a legacy quirk, and whether you meet it in a diff panel or in production depends entirely on the order you do things in. When the legacy share reaches zero, something appears that most engineers go years without experiencing: a delete button for an entire system. Earn it.

Then stay for the second decision: your new service still needs customer data that lives in the legacy, and there are two ways to reach it. Try both and watch what each one does to your clean new code.

Run the migration yourself: build each new service, prove it in shadow, turn the dial, and earn the delete button. Then decide how the new code talks to the legacy that remains.

The Anti-Corruption Layer

The strangling has a subtle failure mode that has nothing to do with routing. It shows up the first time your clean new service needs something the legacy still owns.

The new orders service needs customer data. Customers have not been migrated yet. So the new service calls the old one, and the old one answers in its native tongue: CUST_FLG_7 means active, unless REGION_CD is 9, dates are strings in three formats, and status is an integer whose meanings live in a wiki page last edited six years ago. If your new service consumes that answer directly, those names and rules and quirks take up residence in the new code. Do this for a year and you will have faithfully rebuilt the old system's confusion in a new language, at which point the migration has failed in the way that matters most, and no dashboard shows it.

Eric Evans named the defence in Domain-Driven Design: the anti-corruption layer. It is a translation membrane at the boundary: on the way in, it converts the legacy's model into your model, CUST_FLG_7 = 3 becomes status: active; on the way out, it converts your clean requests into whatever the legacy expects. All knowledge of the old system's vocabulary is quarantined in one deletable place, and your domain never sees a foreign concept. It is worth being precise about what makes this more than an adapter: an adapter matches interfaces so two components can connect; an ACL defends a model, its vocabulary, its shapes, its rules. It is a wall for meaning, not for wiring.

Honesty about cost, in the spirit of every pattern in this course: translation is work. Two models, mapped both ways, kept current. So do not build cathedral ACLs everywhere. When the legacy's model for some piece is actually fine, or the mapping is one renamed field, just conform: adopt their model and move on. Evans named that option too, and choosing it consciously is judgment, not laziness. The ACL earns its cost precisely where the legacy model is rich, strange, and would actively damage your design if it leaked.

And in a strangling, note the ACL's birthday and its funeral: it is transitional architecture. It exists because the legacy still owns data; the day the legacy dies, the ACL is deleted with it. There is one exception worth knowing: when the thing on the far side is not your dying monolith but a permanent foreign system, a vendor API, a partner, a mainframe that will outlive everyone, the same pattern applies and simply never gets the funeral. Permanent border, permanent customs booth.

Diagram titled: a wall for meaning, not wiring. On the left the new service holds a clean model: status active, region EU, an ISO date. On the right the legacy answers in its native tongue: CUST FLG 7 equals 3, REGION CD equals 9, a date in an ambiguous format. Between them the violet ACL translates both ways and is marked: dies with the legacy. Below, a red dashed arrow carrying raw legacy answers is refused at the wall with a large cross. A sky inset offers the alternative: if their model is fine or the mapping is one rename, conform and skip the booth. The dark band closes: adapter matches interfaces, ACL defends a model, and the old vocabulary stays quarantined in one deletable place.

The Endgame, and When Not to Bother

A strangling is only a success at one moment: the delete. The fig's host does not linger; it rots away and the forest moves on. Software versions of the story have a nastier tendency, common enough to deserve its name: the forever-strangler. The first slices migrate fast, because they were chosen to be easy. Then come the hard ones, the boring ones, the ones with no sponsor, and the last 20% of the legacy never leaves. Now you run more than you started with: the old system, the new services, the facade, the sync bridges, the comparison rigs, forever. The scaffolding became load-bearing.

The cures are unglamorous and organizational. The migration has an owner, and a written kill list: every piece of transitional architecture, each with a removal date, tracked like the debt it is. Progress is measured in deletions, not in new services launched, because the count of running systems must go down at the end, and someone must be celebrated for making it go down. If nothing has been deleted in six months, the strangling has quietly become accretion, and it is time to say so out loud.

And complete the judgment by drawing the pattern's own boundary, because the fig is not free and is not always right. All that scaffolding, the facade, the shadow rigs, the sync bridges, the ACLs, costs real engineering. It buys down risk, and the purchase is only worth it when the risk is large: a big system, live traffic, behaviour nobody fully knows. Invert those conditions and the calculus flips. A small system, well understood, lightly used, or one where you can genuinely afford downtime and a redo: rewrite it and cut over, and spend the scaffolding money on something else. The pattern for replacing a system nobody dares touch is not the pattern for replacing a weekend's worth of code.

Takeaways

  • The villain is the instinct, not the legacy. Full rewrites fail for compounding reasons: the world will not pause, the old system is the only spec, sunk cost compounds, and the cutover is one giant bet.
  • The fig's rule: never a gap in the canopy. The old system stays on until the new one already carries the load. Grow inside the traffic, shrink the legacy by attrition.
  • The seam is the front door. A facade that routes everything to the legacy on day one changes nothing and gains you the steering wheel. Slice by capability, start where mistakes are cheap, and keep the dial reversible in seconds.
  • Prove before you cut. Shadow the traffic, serve the old answer, diff the new one. A mismatch is a decision, not a bug report, because the old system is the spec.
  • Traffic cutover and data cutover are separate steps. Dual-writes fork the truth; let changes flow from one writer's log while both systems live.
  • The ACL is a wall for meaning. Translate the legacy's model at the boundary so its ghosts never move into the new walls; conform instead when translation buys nothing. Adapter matches interfaces; ACL defends a model.
  • Scaffolding is transitional, and success is a delete. Track every shim with a removal date, measure progress in deletions, and watch for the forever-strangler.
  • Sometimes, just rewrite. Small, understood, low-traffic, downtime affordable: cut over and keep the scaffolding money.

The fig captured the legacy's traffic, and piece by piece it captured the data too. Which leaves the new fleet holding a question this section has been postponing: now that the data is out, who owns it? One database shared by everyone was the old world's answer, and it is how the old world's problems come back. The next lesson draws the property lines: Database per Service.