DEV Community

Swetha Golla
Swetha Golla

Posted on • Edited on • Originally published at swethagolla-eng.github.io

The Strangler Fig Pattern in Practice: A Step-by-Step Legacy Migration Playbook

By Swetha Golla · 6 min read · Senior Application Architect

🔗 This post has a live interactive version with a clickable stage-by-stage playbook and traffic-shift diagram: read it here

TL;DR

  • Never big-bang rewrite a system that's still earning money. Strangle it: put a facade in front, migrate slice by slice, shift traffic by percentage.
  • The facade/proxy layer is the single most underestimated piece — budget real engineering time for it, not a config file, including a real anti-corruption layer. Without one, the legacy system's mess leaks straight into your clean new system.
  • Route new features to the new system first. You get value in weeks, not after an 18-month rewrite.
  • Every stage must be independently rollback-able. If you can't flip back in minutes, you've built a slow big-bang, not a strangler.

The Setup

You've inherited a 15-year-old order-management monolith at a fictional logistics firm. It processes 40k orders a day, three of the original engineers are gone, and the test coverage is a rumor. Leadership wants it "modernized" by next year, and the loudest voice in the room is proposing a clean-slate rewrite.

That rewrite will fail. Not because the team is bad — because the legacy system encodes a decade of business rules nobody has written down, and a rewrite forces you to rediscover all of them at once, in production, on launch day. The strangler fig pattern rediscovers them one slice at a time, with a rollback lever in your hand. Here's the playbook.

Martin Fowler named this pattern after watching actual strangler fig vines on a trip to Queensland: the vine germinates in the canopy of a host tree, sends roots down around the trunk, and over years grows into its own self-sustaining structure as the original tree dies inside it. Swap "tree" for "monolith" and you have the whole strategy — the new system grows up around the old one, taking over piece by piece, until one day the host isn't load-bearing anymore. It's not a novel idea I'm pitching here; it's a well-worn technique documented in AWS's and Microsoft's own migration guidance, among others, precisely because it keeps working across very different stacks.

The facade/router controls the traffic split at every stage — same seam, only the dial changes: 0% new → 25% → 75% → 100% new, legacy share shrinking the whole time. (See the live diagram for the full visual.)

The Five-Stage Playbook

Every one of these is a shippable milestone with its own risks — treat them as gates, not a Gantt chart.

Stage 1 — Put a facade/proxy in front

Insert a routing layer between clients and the monolith. On day one it does nothing but pass 100% of traffic through — that's the point. Deploy it, watch it in production for a week or two, and make sure latency, auth, and error handling are transparent. Everything else in this playbook depends on this seam existing and being trusted.

⚠ Risk: Added latency and a new single point of failure. If the facade goes down, everything goes down — give it redundancy and monitoring before touching routing rules.

Stage 2 — Route new features to the new system

The monolith is now frozen for new capabilities. Every net-new endpoint or feature is built in the new stack and routed there by the facade. This proves the new system's deploy pipeline, observability, and data access under real traffic — with zero migration risk, because nothing old has moved yet.

⚠ Risk: New features often need legacy data. Read it via a stable interface (API or replicated view), never by reaching into the legacy database directly — shared writes at this stage create coupling you'll pay for in stage 3.

Stage 3 — Migrate slices of old functionality behind the facade

Pick one bounded slice — say, order lookup, not order placement — reimplement it in the new system, and route it through the facade. Start with read-heavy, low-write slices. Run shadow traffic first: send requests to both systems, compare responses, only cut over when they agree. Then repeat, slice by slice.

⚠ Risk: Hidden business rules. The legacy code does things nobody remembers, and each slice will surface a few. Shadow comparison finds them cheaply; a bug report from a customer finds them expensively. Also: slices that share write paths with the monolith need explicit data-ownership decisions.

Stage 4 — Increase traffic % to the new system

For each migrated slice, ramp traffic by percentage: 1% → 10% → 25% → 75% → 100%, holding at each step long enough to see real error rates and tail latency. The facade's routing table is your throttle and your emergency brake. This is where the strangler pattern pays its rent — every step is reversible in one config change.

⚠ Risk: Data written by the new system during a partial ramp. If you roll back to legacy, can it see those writes? Decide the source of truth per slice before ramping, or a rollback quietly loses data.

Stage 5 — Retire the legacy system

When the facade routes 0% of traffic to the monolith, verify with traffic logs — not tribal knowledge — that nothing hits it. Watch for the ghosts: nightly batch jobs, quarterly reports, that one partner integration. Then archive the code, decommission the infra, and hold the retro. This stage is a deliberate project, not an afterthought.

⚠ Risk: Zombie dependencies. Low-frequency consumers (month-end jobs, audit exports) surface weeks after you think you're done. Keep the legacy system dark-but-bootable for one full business cycle before deleting anything.

Strangler Fig vs. Big-Bang Rewrite

Dimension Strangler Fig Big-Bang Rewrite
Risk exposure Bounded per slice — a bad migration affects one route, rolled back in minutes. Total at cutover — every undiscovered business rule fails on the same day.
Time to first value Weeks — first new feature ships on the new stack while the monolith still runs. 12–24 months — nothing ships until everything ships. Scope grows the whole time.
Team disruption Sustained dual-system tax — you run two stacks for the whole migration. But no feature freeze. Feature freeze — the business waits, then demands "just one thing" in the legacy system anyway.
Rollback-ability Built in — the facade is a routing table; flip a percentage back to legacy. Effectively none — rolling back a cutover after data has diverged is a second migration.

Rule of thumb: If the legacy system still serves production traffic and you can't recite its business rules from documentation, you don't rewrite it — you strangle it. Big-bang is only for systems small enough to fully re-spec in under a quarter. This isn't a free lunch: you're trading a single catastrophic risk for a longer migration and a facade layer you now have to run — it's a better trade for most legacy systems, not a risk-free one.

The Part Everyone Underestimates

The facade. Teams treat it as an nginx config and a Friday afternoon. It isn't. It owns authentication passthrough and consistent error semantics — production infrastructure with its own SLA.

It also owns translation, and that job has a name: an anti-corruption layer, a pattern Eric Evans defined in Domain-Driven Design for exactly this situation — two systems with incompatible models that still have to talk. Without one, calls pass straight through and the legacy system's quirks — denormalized tables, overloaded status codes, "status=3 has meant something different since 2019" logic — leak directly into your clean new domain model, and months of migration work end up recreating the same debt in new syntax. Worse, that leakage is exactly what makes stage 5 impossible: you can't retire a legacy system your new one has quietly come to depend on.

When the facade is flimsy, every migration slice becomes a firefight. Staff it like a real component: an owner, tests, observability, a one-line rollback path, and explicit translation logic at the boundary — not an assumption that old and new data shapes match. And be honest about the trade you're making — if the migration stalls (and they do stall, when priorities shift), that facade doesn't disappear. It becomes a permanent extra hop and a second system to maintain. A strangler fig that never finishes is just a new kind of legacy. In my experience, migrations that fail outright don't fail on the new services — they fail because the seam between old and new was treated as an afterthought instead of the thing the whole plan hinges on.

See the working example → A runnable facade-router POC that simulates the 0% → 100% traffic shift. Disagree with the playbook? Tell me where — I'd genuinely like to hear which stage broke for you.

Sources & Further Reading

Top comments (0)