CMD Guide
HomeSystem DesignMicroservices Patterns

The Problem Legacy Systems

The mechanism

A routing facade — a reverse proxy placed in front of the legacy system — intercepts every incoming request and forwards a chosen subset of URL paths to newly built services while everything else still hits the old monolith, so the replacement grows around the original and slowly starves it of traffic until nothing calls it and it can be deleted.

That indirection exists because a legacy system is rarely just bad code you can retype. It runs critical operations at real volume, usually shares one database, and carries unknown unknowns — undocumented behaviour some customer quietly depends on. A big-bang rewrite that flips everyone onto a new system on a Friday night has no incremental rollback: one missed edge case is a full outage, and the whole quarter's rewrite is bet on a single switch. The facade turns that one terrifying switch into hundreds of small, individually reversible ones.

The name is Martin Fowler's, after the strangler fig (a Ficus): it germinates in a host tree's canopy, sends roots down around the trunk, and over years the host rots away, leaving a self-supporting fig in its shape. The new application envelops the old one the same way — the facade is the trunk it grows down.

diagram
diagram

Worked example: peeling the product catalog off a shop monolith

shop.example.com is one deployable, shop-monolith, serving about 2,000 req/s across six endpoints. Browsing the catalog (GET /products) is roughly 1,200 req/s — 60% of traffic and almost all reads. That makes it the safest first slice to peel: high volume (so you learn fast) but low risk (a stale product page hurts far less than a corrupted order).

We put a reverse proxy in front and build a small catalog-svc beside the monolith. The facade config routes only /products, and canaries it at 5% by weighting a shared pool 1:19:

# Two real backends behind the facade
upstream monolith    { server 10.0.0.2:8080; }
upstream catalog_svc { server 10.0.1.10:8080; }

# Canary pool: weight 1:19 sends ~5% of /products to the new service
upstream catalog_canary {
    server 10.0.1.10:8080 weight=1;    # catalog-svc  (new)
    server 10.0.0.2:8080  weight=19;   # monolith     (still authoritative)
}

server {
    listen 80;
    server_name shop.example.com;

    # Migrated slice. Ramp = point this at catalog_svc; roll back = monolith.
    location /products { proxy_pass http://catalog_canary; }

    # Default: everything not yet peeled off stays on the legacy app.
    location / { proxy_pass http://monolith; }
}

To ramp, change one line — point location /products at catalog_svc. To roll back, point it at monolith. Neither backend is redeployed; the migration lives entirely in the facade's config.

The migration, phase by phase

Each phase changes one facade rule and is reversible in under a minute. Loads are steady-state approximations at 2,000 req/s total.

PhaseFacade changecatalog-svc loadmonolith loadRoll back by…
0 · Baselineinstall facade, pure passthrough0 req/s2,000 req/sremoving the facade
1 · Shadowmirror GET /products to catalog-svc, discard its reply, diff the two responses in logs~1,200 req/s (mirrored; users never see it)2,000 req/s (authoritative)deleting the mirror directive
2 · Canary 5%weight pool 1:19~60 req/s~1,940 req/ssetting weight to 0 / repointing
3 · Ramp reads/productscatalog_svc~1,200 req/s~800 req/s (40%)repointing /products to monolith
4 · Next sliceadd /cartcart-svc+~300 req/s (cart)~500 req/srepointing /cart
N · Retireno location points at monolith2,000 req/s across new services0 req/skeep the image 30 days, then delete

Notice the shape: reads move first (phases 1–3) because they are idempotent and cheap to mirror and diff; writes and the harder endpoints come later, once the new store has proven it returns the same answers.

Pitfalls

Why the naive version is wrong. The easy read of this pattern is "route HTTP paths." But if catalog-svc and the monolith both read and write the same products table, you have split nothing: the two now race on the same rows, dual writes silently clobber each other, and you own a distributed monolith with none of the isolation you paid for. The path split is the visible 10%; the real work is splitting the data — give the new service its own store and sync it via change-data-capture or events, or run a shared-DB step first and cut the seam deliberately. Route writes to the new service only once its store is authoritative.

When to use it — and when not

Reach for Strangler Fig when the system is large and business-critical, a big-bang cutover is unacceptable, you can identify seams (endpoints or bounded contexts) that peel off cleanly, and you need to keep shipping features throughout. It shines precisely when behaviour is partly undocumented, because the shadow phase lets you verify each slice against real traffic before you trust it.

Do not reach for it when the system is small enough that a full rewrite fits in one release cycle (the facade and the long dual-run tax cost more than they save), when the seams simply do not exist (one giant tangled transaction that touches everything — you cannot peel a slice), or when the legacy is being decommissioned entirely because its users are leaving (just freeze it).

Trade-offs vs a big-bang rewrite. Strangler buys incremental rollback, continuous delivery, early feedback, and a small blast radius. It costs you running both systems for months or years — double infra, double the mental model — plus building and operating the facade and the data-sync glue. A big-bang rewrite buys a clean-slate design and no dual-run tax, but bets the business on one cutover where a single defect is a full outage (the graveyard of failed rewrites). Choose Strangler when downtime is unacceptable and the system is large; prefer big-bang when the system is small, well-understood, and a short freeze is fine.

Trade-offs vs an anti-corruption layer (encapsulate and leave). Here you wrap the legacy behind a clean API and stop — no replacement. It is the cheapest option with near-zero migration risk, but every underlying problem (scaling ceiling, security posture, nobody left who knows COBOL) survives untouched. Choose this when the legacy actually works and only its edges hurt; choose Strangler when the core itself must go. Note that parallel run — serving both, comparing outputs, trusting the old one until confident — is not really a competitor: it is the shadow phase inside a strangler migration.

Takeaways


Sources: Martin Fowler, "StranglerFigApplication" (martinfowler.com, 2004; renamed 2019); Sam Newman, Monolith to Microservices (O'Reilly, 2019), ch. 3 on the strangler fig, parallel run, and branch by abstraction; Chris Richardson, microservices.io — "Strangler Application" pattern. Re-authored and deepened for this guide, with a worked NGINX facade example.

🤖 Don't fully get this? Learn it with Claude

Stuck on The Problem Legacy Systems? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **The Problem Legacy Systems** (System Design) and want to truly understand it. Explain The Problem Legacy Systems from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **The Problem Legacy Systems** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **The Problem Legacy Systems** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **The Problem Legacy Systems** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes