How We Split a Legacy Monolith Into Microservices Without a Single Outage

작성자

카테고리:

← 피드로
DEV Community · Java Only · 2026-07-21 개발(SW)

8 min read · telecom provisioning platform, 30M+ subscribers

Most companies avoid migrating their monolith for one reason: they imagine it as a single, terrifying event — months of a feature freeze, a weekend cutover, and a rollback plan that’s really just hope. That fear is reasonable. A big-bang rewrite of a live system serving 30M+ subscribers really would be terrifying.

So we didn’t do that. We used a pattern that lets you migrate a monolith one slice at a time, while the system keeps running and the product team keeps shipping features — the same pattern Martin Fowler named Strangler Fig, after the vine that grows around a host tree, gradually taking over, until eventually the original tree is no longer needed.

Why the “just rewrite it” instinct is usually wrong

The instinct to rewrite a legacy monolith from scratch is understandable — the old code is scary, undocumented, and nobody wants to touch it. But a full rewrite has a well-known failure pattern: it takes far longer than estimated, the business can’t freeze feature development for that long, and by the time the rewrite is “done,” the old system has changed underneath it and the rewrite is already out of date.

The alternative isn’t “don’t migrate.” It’s: migrate in slices small enough that each one is boring, and never require the business to stop shipping while you do it.

The pattern: a facade, and one slice at a time

Strangler Fig works by putting a routing layer — a facade or API gateway — in front of the monolith. At first, 100% of traffic passes through to the old system untouched. Then, one capability at a time:

  1. Build the new version of that one capability as an independent service.
  2. Update the facade to route just that capability’s traffic to the new service.
  3. Run both in parallel long enough to trust the new one (see “shadow traffic” below).
  4. Retire that piece of the old monolith. Repeat for the next capability.

At every point in this process, the system is fully functional. There’s no “half-migrated and broken” state — there’s only “some capabilities on the new architecture, some still on the old one,” which is a perfectly stable place to be for months if needed.

Picking what to peel off first

This is the decision that determines whether the whole migration feels safe or risky: start with the slice that’s high-value but low-risk, not the scariest, most business-critical piece. A good first slice is:

  • Reasonably self-contained (doesn’t touch five other modules)
  • Has a clear, testable boundary (you can tell if the new version is behaving correctly)
  • Matters enough that finishing it proves the pattern works — but isn’t the piece that takes down the whole business if something goes wrong

Getting one real slice fully migrated — end to end, safely — does more for organizational confidence than any planning document. After the first slice, the second one is always easier, because now there’s a proven playbook instead of a theory.

The detail most teams skip: an anti-corruption layer

When the new service needs to talk to parts of the monolith that haven’t been migrated yet, it’s tempting to just reach into the old database directly. Don’t. That recreates the exact coupling you’re trying to get away from, and now the “new” service is just as entangled as the old one.

Instead, use an anti-corruption layer — a thin translation layer that lets the new service talk to the monolith through a clean interface, without either side needing to understand the other’s internal model. It’s a bit more upfront work. It’s the difference between a migration that gets easier over time and one that gets more tangled with every slice.

How to know it’s safe to cut over: shadow traffic

Before fully switching a capability to the new service, run both versions in parallel: real production traffic hits the old path (which still returns the actual response), while the same traffic is also sent to the new service in “shadow” mode — its output is logged and compared, but never returned to the user. If the new service produces the same result as the old one across thousands of real requests, you’ve earned the confidence to cut over for real. If it doesn’t, you find out with zero customer impact.

This is the single biggest reason this approach avoids outages: you’re never trusting a new implementation on faith. You’re trusting it because you already watched it agree with the old one thousands of times.

The cutover itself should be the least interesting part of the entire migration — because by the time you do it, you already know what’s going to happen.

Sequencing: what actually happened, wave by wave

In practice, this played out as a small number of clearly-scoped waves rather than one continuous effort:

  • Wave 0 — foundations. Facade/gateway in place, observability wired up so we could actually see what “correct” looked like before changing anything.
  • Wave 1 — first slice. The self-contained, provable capability. Shadow traffic, then cutover. This is where the team builds real confidence, not theoretical confidence.
  • Wave 2+ — the rest, in priority order. Each subsequent slice reuses the same playbook: build, shadow, compare, cut over, retire the old path.
  • Final step — decommission. Once every capability has moved, the facade has nothing left to route to the old monolith, and it’s retired for good.

Nowhere in that sequence is there a step called “freeze feature development while we rewrite everything.” The product team kept shipping the entire time, because from their point of view, nothing was frozen — just steadily, quietly getting replaced underneath them.

What I’d tell someone starting this today

  • Your first slice is a confidence-building exercise, not just a technical one. Pick one that can actually succeed cleanly.
  • Never let a new service reach directly into the old system’s data. The anti-corruption layer is what keeps the migration from re-creating the same mess.
  • Shadow traffic isn’t optional if you want zero-downtime confidence. Watching the new path agree with the old one, on real traffic, is what makes cutover boring instead of scary.
  • A migration that requires freezing the product roadmap is a sign the slices are too big. Break it down further.

The business case for this isn’t just “safer engineering.” It’s that your team never has to choose between modernizing the system and shipping the roadmap — because with this pattern, you were never actually choosing between the two.

Full post with more detail: [https://thinh-cloud.netlify.app/blog/strangler-fig-migration]
Book a 20-min call: https://cal.com/thinh-le-jvm/20mins

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다