Strangling a Monolith Without a Freeze Window
Rewrites fail because the specification is the running code. The alternative is a sequence of small, individually reversible extractions — and a characterisation suite that makes behaviour change visible rather than accidental.
Every long-lived system reaches the point where the cost of the next feature is dominated by the cost of not breaking the last ten. The instinct is to rewrite. The instinct is usually wrong, for a reason that has nothing to do with ambition: in a system that has run a business for a decade, the specification is the running code, including the parts of it that are arguably bugs and that someone downstream now depends on.
The alternative is to strangle it — replace it incrementally, boundary by boundary, with the old system live throughout.
Pin the behaviour first
Before anything is extracted, write characterisation tests. These are not tests of what the system should do. They are tests of what it currently does, captured by running real inputs through it and recording the outputs, oddities included.
This feels wrong the first time. You are enshrining behaviour you believe to be incorrect. That is precisely the point: the tests are not a quality statement, they are a change detector. When an extraction alters an output, you want that to appear as a failing test and a deliberate decision, not as a support ticket six weeks later.
Prioritise by traffic. The paths that carry the most volume and the most money get characterised first, in depth. The long tail gets sampled.
Put a façade in front
Route all traffic through one layer you control — a reverse proxy or a thin routing service. Initially it forwards everything to the monolith and does nothing else. It is not a rewrite; it is a seam. Without it, every extraction requires touching every caller, and the migration becomes a coordination problem across teams rather than an engineering problem inside one.
The façade is also where you get your first honest traffic map. Most teams discover endpoints they believed were retired.
Choose boundaries by change frequency times blast radius
Not by architectural elegance. The candidate worth extracting first is the one that changes often and, when it breaks, breaks a lot. That is where the ongoing cost lives, and where the return on the extraction is realised soonest.
Deliberately avoid starting with the hardest, most tangled core. Start adjacent to it. Each extraction removes some of the coupling that makes the core hard, and by the time you reach it, it is a smaller problem than it was.
Move data with dual-write and shadow-read
Data is where these migrations actually fail. The safe sequence has four steps, and skipping any of them is where the incident comes from.
Write to both stores while the old one remains authoritative. Then read from the new store in the background and compare the result against the old one, logging every divergence without serving it. Fix divergences until the comparison is quiet for a meaningful period — a full business cycle, including month-end, not a quiet Tuesday. Only then switch reads over, keeping dual-write until you are ready to commit.
Run the reconciliation continuously rather than once. A one-off migration script proves the state at one instant; a reconciliation job proves that the two systems agree under live traffic, which is the property you actually need.
Keep every step reversible
Each extraction should be revertible by a routing change, and nothing else. If reverting requires a data restore, the step is too large — decompose it.
This is what removes the freeze window. There is no cutover weekend because there is no cutover: there is a sequence of routing changes, each small enough to undo before the standup finishes. Feature work continues in parallel, which matters more than it sounds. A migration that requires the business to stop is a migration the business will eventually cancel.
Decommission deliberately
An extracted boundary is not done until the old code path is deleted. Dark code accumulates, and a dead route left in place for a year will be resurrected by someone who does not know it is dead.
The rule that works: once a route has served zero traffic for a full business cycle, delete the implementation, not just the route. Then update the diagram — because the diagram that no longer matches the system is the reason the next team will propose a rewrite.