Monolith to Microservices: A Step-by-Step Migration Guide
Monoliths are not a mistake. Most should stay monoliths. But when a ten-year-old application needs a full-system deploy to change a button colour, you have a velocity problem — and that is a reason to decompose, where "it feels dated" is not.
Should you migrate at all?
Start here, because the honest answer is often no. Microservices trade a simple deployment story for a distributed systems problem: network failures, partial outages, no cross-service transactions, and far harder debugging. You are buying independent deployability and paying in operational complexity.
Decompose when at least two of these are true:
- More than roughly 20 engineers are contending for the same deploy pipeline.
- Release cadence is slower than weekly because of coordination, not testing.
- One component's load forces you to scale the entire application.
- Distinct parts genuinely need different runtimes or data stores.
If none of those apply, a well-modularised monolith will serve you better and cost less to run.
The strangler fig sequence
Named for the vine that grows around a tree until it stands on its own, this is the migration order we use. Do it once per domain, and finish each pass before starting the next:
- Put a gateway in front. All traffic routes through an API gateway while the monolith still serves every request. Nothing has changed functionally — you have simply gained a seam to redirect at.
- Pick a low-risk, low-coupling domain. Notifications, PDF generation, search indexing. Not billing, and not authentication.
- Build the service, and its data boundary. The service owns its own storage. If it still reads the monolith's tables directly, you have built a distributed monolith — the worst of both designs.
- Shadow the traffic. Send real requests to both paths, compare the outputs, and fix the differences before anything depends on the new service. This is the step most teams skip and most regret skipping.
- Cut over behind a flag, so reverting is a config change rather than a deploy.
- Delete the old code path. Not later — now. Dead code left in the monolith is the reason migrations stall half-finished and leave you maintaining both designs forever.
- Repeat with the next domain.
Planning a migration?
Our cloud architects can review your decomposition roadmap before you commit to it. Learn about our cloud services.
What breaks, and what to do about it
| What breaks | Why | Remedy |
|---|---|---|
| Debugging | One request now spans several services and logs | Distributed tracing via OpenTelemetry, from day one |
| Transactions | ACID guarantees do not cross service boundaries | Saga pattern with explicit compensating actions |
| Data ownership | Shared tables recreate the coupling you removed | One datastore per service; integrate through APIs or events |
| Local development | Running twelve services on a laptop stops being practical | Containerised stack plus contract tests, not full local parity |
What does a migration cost, and how long does it take?
The cost of a migration is rarely in writing the new services. It is in running two architectures at once, and that overlap is the line most plans omit. While the strangler fig is in progress you pay for duplicated infrastructure, data kept consistent across both sides, and engineers holding two mental models of the same system. Budget for that period explicitly rather than treating it as a transition cost that will somehow not arrive.
There is also a permanent operational cost on the far side, and it is the one that catches teams out. Microservices need things a monolith did not: distributed tracing, centralised logging, service discovery, per-service CI pipelines and someone on call who understands the topology. If you do not already have that platform capability, you are funding two projects — the migration and the platform underneath it.
A rough shape for a mid-sized application:
- Months 1–2: instrumentation and seams. Add tracing and logging to the monolith first, because you cannot safely extract what you cannot observe.
- Months 3–6: extract two or three services at the edges — notifications, reporting, file processing. Low coupling, real learning, and reversible if the approach is wrong.
- Months 6–12: the hard middle. Services that share the database with the monolith. This is where most migrations stall, and where the data model decisions get made.
- Ongoing: stop when the pain that justified the migration is gone. A permanently partial migration is a legitimate end state, and a far better outcome than a complete one nobody needed.
That last point is the one worth taking away. The goal was never to eliminate the monolith; it was to stop it blocking delivery. If extracting three services achieves that, extracting twelve is cost without benefit.
It is worth being blunt about the failure mode, because it is common and expensive: a team adopts microservices to fix a delivery problem that was actually a testing problem or an ownership problem, and arrives at a distributed system with the original bottleneck intact plus a great deal more operational surface. If releases are slow because the test suite takes two hours, faster tests are a far cheaper fix than a new architecture. Diagnose the constraint before you change the topology.
For the infrastructure side of this — assessing the cost baseline first, and deciding rehost versus refactor per workload rather than for the whole estate — see our cloud solutions page, or talk to us about where your delivery is actually blocked. We would rather tell you the constraint is your test suite than sell you an architecture programme you do not need.
The bottom line
Microservices buy deployment independence, and they are an investment in team speed rather than in raw performance. Migrate one domain at a time, keep each step reversible, and delete the old path every time. If your team is under twenty engineers and shipping weekly, the better architectural decision is usually to modularise what you have.