AriesDevs

Engineering

Moving a decade-old monolith to services without a rewrite

The strangler pattern in practice: what to move first, what to leave alone, and how to keep shipping weekly releases the whole time.

Aisle of server racks in a teal-lit data centre

AriesDevs engineering team Engineering notes

4 min read

Picture a typical case: a monolith that runs a logistics business. It is twelve years old, has around 600,000 lines of code, and a release process that needs a full weekend. The team wants microservices. The business wants to keep taking orders. A rewrite would mean two years of building something that earns no revenue, so the better route is not to do one.

The strangler fig approach works well here: grow new services around the old system, redirect traffic to them one capability at a time, and let the monolith shrink until the remaining pieces are small enough to ignore or retire. It sounds tidy on a slide. These are the decisions that make it work in practice.

Put a facade in front first

Before writing any new service, place a thin routing layer in front of the monolith. On day one it forwards every request unchanged. That single step gives you a place to redirect traffic later, a place to measure latency per endpoint, and a rollback switch that does not need a deployment.

nginx
# everything goes to the legacy app by default
location / {
    proxy_pass http://legacy-app;
}

# invoices extracted first, as an example
location /api/invoices/ {
    proxy_pass http://invoice-service;
}
The facade starts as a pass-through. Moving a capability is a one-line change.

Choose the first slice by pain, not elegance

Engineers tend to pick the cleanest module to extract first. That teaches you the least. Score candidates on three questions: how often does this area change, how often does it break, and how few database tables does it share with the rest of the system? In a case like this, invoicing is a likely winner: it changes often because of tax rules, causes a disproportionate share of incidents, and touches few tables.

Leave the order-entry core alone for as long as you can. It may be ugly, but it is stable and every other part of the system depends on it. Extracting it early is the riskiest move with the smallest payoff.

Keep the data honest

The hard part of any extraction is the database. A rule of three phases works for each slice.

  1. Dual write: the monolith keeps writing to its tables and also publishes an event. The new service builds its own store from those events. Nothing reads from the new store yet.
  2. Shadow read: the facade sends reads to both systems and compares results. Differences are logged and fixed until the mismatch rate stays below 0.1% for two weeks.
  3. Cut over: reads move to the new service, then writes. The old tables become read-only, and are dropped only after a full quarter-end has passed without anyone touching them.

The shadow-read phase is where the real requirements show up. Rounding rules, time zone assumptions and a spreadsheet export that finance relies on are typical behaviours nobody has written down.

Keep releases boring

The migration only works if everything else continues as normal. Keep the weekly release train for the monolith and give each new service its own pipeline from the start. Feature flags decided who saw the new path: internal staff first, then 5% of customers, then everyone. Every cut-over had a written rollback that had been rehearsed in staging.

If the team cannot describe the rollback in one sentence, the slice is too big.

Know when to stop

After many months, the main capabilities live in their own services and the monolith is a fraction of its original size. The remaining code is stable, rarely changed and cheap to run. There is no need to extract it if no business case justifies the cost. A migration is finished when the pain is gone, not when the monolith is gone. Agree that definition with the business at the start, or you will be asked to keep going forever.

Talk to an engineer

Have a project like this?
Let us talk it through.

Bring the problem, not the solution. A senior engineer will give you an honest view of the options, the trade-offs and a realistic cost range.