A 15-year-old monolith must be modernised without a rewrite and without a feature freeze. Plan the migration.
Show the full answer Hide the answer
Establish why, before deciding what
"Modernise" is not an objective. The reasons that justify the cost are specific: delivery is too slow; the technology is unsupported or unhireable; it cannot scale where it needs to; it cannot meet a compliance requirement.
The reason determines what to move first, and a migration without one drifts into a rewrite with extra steps.
Find the seams
An incremental migration needs points where traffic for one capability can be redirected while everything else continues. In order of convenience: an HTTP boundary for a routing facade; a message boundary where a new consumer subscribes alongside the old; a module or interface boundary for an implementation swap; a database trigger or CDC feed when nothing else exists.
If there is no seam, create one. Introducing a facade that initially forwards everything unchanged is frequently the highest-value first increment: it converts an all-or-nothing rewrite into a sequence of small, reversible moves.
Sequence by value and risk
First: something with clear value, moderate complexity, and few dependencies — it proves the approach, builds the pipeline and the observability, and earns the credibility for the rest.
Not first: the hardest, most entangled core. That is where migrations die, and where the team has the least experience with the new stack.
Also early: anything blocking the stated objective. If the reason is scaling, move the component that cannot scale.
Solve the data problem, because it is the harder half
A migrated capability usually needs data still owned by the legacy store, for months.
Options, in ascending order of independence: read from the legacy database behind an anti-corruption layer — quick, and it keeps the coupling; CDC into a read model the new service owns — good, and eventually consistent; dual write during transition — precise, and it must be an outbox or an equivalent, never two independent writes; full ownership transfer with the legacy system reading back via an API.
The anti-corruption layer is not optional here. CDC output carries the legacy schema's names, encodings and accumulated oddities, and it must not become the new system's model — or forty years of decisions propagate forward and the modernisation has failed at the start.
Run both, deliberately
Route with a facade and a feature flag per capability, so a rollback is instant.
Consider parallel run for high-risk capabilities: both implementations execute, the old one serves, and outputs are compared. Expensive, and the most reliable correctness evidence available.
Then progressive traffic shift, watching error rate and latency against the old path.
Finish it
Delete the old path. A strangler migration that never removes anything has produced two systems and doubled the maintenance — the most common way this fails.
Track a visible metric: percentage of traffic, or of capabilities, on the new system. Migrations without a published number lose funding when the initial enthusiasm passes.
Say what may not happen
Some of the monolith may never move, because the value does not justify the cost. That is an acceptable outcome and should be stated at the start, so the programme is judged on the objective rather than on reaching zero.