From a system you fear to a system you can change.
Published 14 October 2026
The rewrite is tempting for good reasons
When a system is hard to change, a rewrite looks like relief: a clean start, modern tools, no history. The reasons for wanting one are usually real. So are the risks. A rewrite has to reproduce, before it can replace, all the behaviour the old system accumulated, much of which nobody documented because it was never a decision, only a fix. Meanwhile the business does not stop, so the old system keeps changing and the target keeps moving.
None of this means a rewrite is never right. It means the case for one should be made against the alternative of evolving what exists, with the costs of both written down. Most of the time the evolutionary path is cheaper to start, easier to stop and delivers value earlier.
Find the seams
A seam is a place where you can change behaviour without editing everything around it. In an existing system, seams are rarely where the diagram says they are. They are discovered by following change: which parts always change together, which parts nobody dares to touch, which interfaces carry the most traffic or the most incidents.
Once a seam is identified, make it explicit. Put a contract around it: an API definition, a message schema or, at the very least, a set of characterization tests that record what the code does today. Now both sides can change independently as long as the contract holds, and a failing check tells you when it does not.
The strangler pattern follows from this. New behaviour is built behind the seam while the old implementation keeps serving traffic, and responsibility moves across slice by slice. It works because each slice is small enough to finish.
Make every change reversible
The most useful property of an evolutionary migration is that you can stop, or go back. That does not happen by accident. It comes from a few habits.
Feature flags or routing rules let new code run for a fraction of traffic, or for a single customer, before it runs for everyone. Running old and new paths in parallel and comparing results exposes differences you did not anticipate. Database changes follow an expand-and-contract sequence: add the new structure, write to both, migrate the readers and only then remove the old structure, so that each step can be undone.
The test for a release is simple: has someone actually reversed it in a lower environment? If the rollback path has never been exercised, it is a theory.
Record why, not only what
Architecture erodes when the reasoning is lost. A new engineer sees a strange boundary, assumes it was an accident and removes it. Lightweight architecture decision records prevent that. Each one states the context, the decision, the alternatives considered and the consequences accepted, in a page or less, stored next to the code.
Records also make disagreement productive. When a decision no longer fits, the team can see which assumption changed and replace the record, rather than silently drifting away from it.
When a rewrite is the right call
Sometimes it is. The platform may be unsupported, the data model fundamentally wrong for the business, or the cost of every change dominated by something no seam can isolate. Even then the same techniques apply: define the contract the new system must honour, migrate in slices where possible, run both in parallel and keep the exit open.
If you are weighing the options, an independent assessment of the current system is often the cheapest step. It turns an argument about taste into a comparison of costs and risks.