Lesson 2 of 2
Sequencing a Split So Each Step Is Reversible
A migration that must complete before anything improves is a migration that gets cancelled. Order the work so every step delivers value and can be undone.
Splits fail for organisational reasons as often as technical ones. A two-year programme delivering nothing until month eighteen will be cancelled at month eleven when priorities change, leaving a system half-way between two architectures — which is worse than either. Sequencing is therefore part of the design, not project management detail.
An order of operations that keeps every step useful
- 1
Enforce module boundaries inside the monolith first
Valuable on its own, and it converts extraction from archaeology into a move. If you cannot enforce a boundary in a codebase you fully control, you will not hold it across a network.
- 2
Give each candidate module its own schema
Also valuable on its own: it surfaces every cross-module query as an explicit contract. This is where the real coupling is discovered, and discovering it now is far cheaper than during a migration.
- 3
Convert cross-module calls to events where the caller does not need a result
Reduces the number of interactions that will become synchronous network calls. Each conversion is independently deployable and independently reversible.
- 4
Extract one module, keeping the old path behind a flag
Run both paths, compare outputs, then switch traffic gradually. Being able to switch back in minutes is what makes the step safe enough to take at all.
- 5
Operate it for a quarter before extracting anything else
You are testing the organisation, not the code: on-call load, incident response across a boundary, deployment friction, and whether the promised benefit actually materialised.
- 6
Re-run the decision method with what you learned
The cost inputs are now measured rather than estimated. It is entirely legitimate — and fairly common — for the answer after one extraction to be "this is enough".
Two ways to move traffic during an extraction
Routing switch
A flag or gateway rule sends a percentage of traffic to the new service; the rest continues to the monolith.
- Instant rollback by changing one value
- Load can be increased gradually with real traffic
- Simple to reason about and to explain during an incident
- Both implementations must exist and be maintained for a period
- Data written by each path must remain compatible
Choose when: The operation is idempotent or read-heavy, and both paths can safely write to the same data during the transition.
Parallel run with comparison
Both paths execute for every request; the old result is returned while the new one is recorded and compared.
- Discovers behavioural differences before any user sees them
- Builds confidence for high-risk paths such as pricing or grading
- Produces evidence rather than an assurance
- Double the work per request during the run
- Side effects must be suppressed on the new path, which is fiddly
- Comparison logic and a place to store discrepancies
Choose when: Correctness matters more than effort — money, grading, compliance — and a silent behavioural difference would be expensive.
In practice
A migration that survived a change of priorities
A retail platform planned to extract four capabilities over eighteen months. At month seven the company acquired a competitor and all discretionary engineering effort was redirected to integration work for two quarters.
Constraints
- Four planned extractions, one complete at month seven
- Two quarters of near-zero capacity for the migration
- The remaining three capabilities still in the monolith
- Module boundaries and per-module schemas already in place
Decision
Stop after the first extraction and declare the intermediate state a supported architecture rather than a temporary one. Document it, keep the modular monolith as the default home, and treat the remaining three extractions as independent future decisions rather than as a committed plan.
Why
Because every step had been independently valuable, the system at month seven was in a coherent state: enforced modules, separate schemas, one extracted service for the component with a genuine scaling need. Nothing was half-migrated. Had they sequenced the work as a single programme with value only at the end, the pause would have left a system with two half-owned data models and no clear operating story.
What it cost
The three remaining capabilities never were extracted, and two years later that still looked like the right outcome — the scaling and blocking signals that would have justified them never materialised. The cost was some wasted design work on the three unbuilt services, and a period of explaining to new joiners why the architecture was "inconsistent". The team's recorded lesson was that an architecture that stops cleanly at any point is worth more than one that is optimal only when finished.
Asked for a migration timeline
As a developer
Estimates the extraction work per capability and sequences by dependency order, producing a plan that finishes in a defined number of quarters.
As an architect
Sequences by value and reversibility instead, and states plainly that the plan should be expected to stop early — because priorities change, and because the evidence after the first extraction may say stop. The question to answer for each step is "if we stopped here, would the system be coherent?" A plan that answers yes at every step survives an acquisition, a funding round or a change of leadership; a plan that answers yes only at the end is a bet on eighteen months of organisational stability.
During an extraction, your team proposes writing every order to both the monolith database and the new service database so that either can serve reads during the transition. What is your concern, and what would you do instead?RevealHide
There is no transaction spanning the two stores, so any failure of the second write leaves them silently divergent — and the divergence is discovered later, usually by a customer. Prefer one of two designs. Write to one store and derive the other from its change events, so there is a single source of truth and a defined lag rather than an undefined disagreement. Or use a routing switch so that exactly one path writes at a time, and move traffic gradually, which gives you rollback without ever having two writers. If a dual-write really is unavoidable for a short window, build the reconciliation job first and alert on any discrepancy, because the whole danger of this pattern is that it fails quietly.
Key takeaways
- Sequence a split so every step is independently valuable and the system is coherent if the work stops.
- Enforce boundaries and separate schemas before extracting anything; both pay off regardless of the outcome.
- Use the strangler fig approach with a routing switch, and keep rollback to a single configuration change.
- Parallel running with comparison is worth its cost on paths where a silent behavioural difference is expensive.
- Avoid dual writes without a reconciliation mechanism; two stores with no shared transaction diverge silently.