CMD Guide
HomeSystem DesignMicroservices Patterns

Key Insights and Implications

A Strangler Fig migration works by putting a routing proxy in front of the live monolith so that each incoming request can be sent, independently, to either the old code path or a new service — letting you retire the legacy system one capability at a time while it keeps serving traffic. That routing layer is the easy part. The insight that separates a migration that finishes from one that stalls at "80% done" for two years is this: the old and new code paths still operate on the same business data, and for the entire coexistence window you must keep that data consistent across a seam that now spans two systems. A Strangler migration is a data problem wearing a routing costume.

diagram
diagram

How the proxy actually decides

The router evaluates a rule per request — a canary percentage (route 25% of checkout traffic to the new service), a feature flag, a user-segment cohort, or a URL/header match — and forwards accordingly. This gives you a dial you can turn from 0% to 100% and, critically, back to 0%. But note what the dial does not do: flipping the proxy back to the monolith does not un-write the orders the new service already persisted. Routing is reversible; data is not. Everything hard about Strangler lives on the write path.

Worked example: strangling the Checkout capability

We extract CheckoutService from a live e-commerce monolith. The monolith writes to legacydb; the new service owns checkoutdb. During the window, the monolith's still-live "Order History" page reads legacydb, so any order the new service creates must appear there too. The tempting first cut is to have the new service write to both databases. Trace what happens to order #4021 (total ₹2,400) when the second write fails:

StepOperationcheckoutdblegacydb
1CheckoutService: INSERT order 4021; COMMIT4021 PLACED
2Open connection to legacydb4021 PLACED
3legacydb failover / network blip → INSERT times out4021 PLACED— (lost)
4Customer sees "Order placed" (served by new service)4021 PLACED
5Support opens Order History (served by monolith)4021 PLACEDno such order

The two systems now disagree permanently. The customer was charged; the monolith has no record; reconciliation is a manual ticket.

Why the naive version is wrong

Two independent commits to two datastores are not one transaction. There is no point at which "both or neither" is guaranteed — a crash, timeout, or deploy between the commits leaves the systems diverged with no automatic recovery. You cannot buy atomicity across two databases without a distributed transaction (2PC), and 2PC trades away exactly the availability the Strangler pattern exists to protect.

The fix: single writer + transactional outbox

Make checkoutdb the sole writer for the checkout capability. The order row and an outbox row are written in one local transaction (same database, real atomicity). A separate relay publishes the outbox event and a legacy-side consumer applies it idempotently. Propagation is asynchronous and at-least-once, so duplicates are expected and made harmless with an upsert.

placeOrder(order):                          # WRONG — dual write, no atomicity
    checkoutDb.exec("INSERT INTO orders(id,total,status)
                     VALUES (4021, 2400, 'PLACED')")   # commits
    legacyDb.exec("INSERT INTO orders ... VALUES (4021, ...)")  # may fail

placeOrder(order):                          # RIGHT — outbox in the same tx
    tx = checkoutDb.begin()
    tx.exec("INSERT INTO orders(id,total,status) VALUES (4021, 2400, 'PLACED')")
    tx.exec("INSERT INTO outbox(event_type, payload)
             VALUES ('OrderPlaced', :orderJson)")
    tx.commit()          # order + event committed atomically, or neither

# relay (separate process): poll unsent outbox rows,
#   publish to Kafka, mark sent          -- at-least-once
# legacy consumer:
#   INSERT INTO orders(...) VALUES (4021, ...)
#   ON CONFLICT (id) DO NOTHING          -- idempotent, duplicate-safe

Now re-run the failure: if the relay or consumer is down, the outbox row simply waits and is retried; a duplicate delivery hits ON CONFLICT DO NOTHING and is a no-op. The systems converge instead of diverging. This is the same mechanism as the guide's Transactional Outbox and Event-Driven Architecture pages — the Strangler window is where you first feel why you need it.

Pitfalls

When to use it — and when not

Reach for Strangler Fig when the legacy system is large enough that a rewrite would take many months, it must stay live throughout (it's revenue-critical, you can't take a maintenance window), and it has identifiable seams — bounded capabilities like "checkout" or "profile" whose data you can carve out. Concrete signals: "the monolith is 500k LOC," "we ship features weekly and can't freeze," "one bad cutover would page the whole company."

Trade-offs versus the alternatives:

The sub-decision inside Strangler — shared database (fast to build, but schema coupling defeats the point) versus single-writer + CDC/outbox (true decoupling, but you buy eventual consistency and pipeline infra). Default to single-writer + outbox for any capability you actually intend to own; use a shared DB only as a short, dated transitional stage.

Takeaways


Re-authored and deepened for this guide. Sources: Martin Fowler, "StranglerFigApplication" (martinfowler.com, 2004); Sam Newman, Monolith to Microservices (O'Reilly, 2019), esp. the chapters on incremental decomposition and splitting the database; Chris Richardson, Microservices Patterns and microservices.io (Strangler, Transactional Outbox, and Change Data Capture patterns).

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

Stuck on Key Insights and Implications? 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 **Key Insights and Implications** (System Design) and want to truly understand it. Explain Key Insights and Implications 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 **Key Insights and Implications** 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 **Key Insights and Implications** 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 **Key Insights and Implications** 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