Every monolith we have inherited came with the same pitch: "we will rewrite it properly this time." We have stopped believing that pitch. The big-bang rewrite competes with the old system for years, and the old system keeps shipping features while the new one catches up. The strangler approach wins because it never asks the business to wait.
Start with a seam, not a service
The instinct is to pick the most painful module and extract it first. That is usually the wrong move, because the most painful module is painful precisely because it is tangled into everything else. We look instead for a seam: a piece of behavior with a clean input and a clean output, ideally one that is read-heavy so a wrong answer is cheap to detect.
On the payments monolith, our first extraction was not payments at all. It was the merchant statement generator - a nightly job that read data and produced PDFs. Nothing downstream depended on it being fast, and if it broke, nobody lost money.
- Pick a seam with few callers and clear boundaries
- Prefer read paths first so failures are observable, not destructive
- Route through a facade so callers do not know where the logic lives
- Keep the old code in place until the new path has run in production for weeks
The facade carries the migration
We put a thin routing layer in front of the behavior being moved. Internally it can call either the old in-process code or the new service. A config flag decides which. This is what lets us move traffic gradually - one percent, then ten, then everything - and roll back in seconds when something looks off.
if (flags.isEnabled("statements.use-new-service", merchantId)) {
return statementClient.generate(merchantId, period);
}
return legacyStatementService.generate(merchantId, period);A strangler migration that cannot roll back in one config change is not a migration, it is a bet.
Where it goes wrong
The failure mode we have hit twice is leaving the facade in place forever. The new service ships, traffic moves over, everyone celebrates, and the old code path stays "just in case" for eighteen months. Now you maintain two implementations and the seam you built has become its own monolith. We now treat removing the old path as part of the work, with a date on the calendar, not an afterthought.
The other trap is shared database tables. If the new service and the old monolith both write the same rows, you have not split anything - you have added a network hop to a distributed monolith. We split the data or we do not call it done.