Monolith to microservices, one bounded context at a time
Boundaries chosen from measured coupling and data ownership, extraction ordered by risk, and the monolith live in production the whole way through. Including an honest answer about when you shouldn't do this at all.
When microservices are the wrong answer
Worth putting first, because a firm that sells the same architecture to everyone isn't giving you advice.
Microservices solve organisational problems more than technical ones. They let separate teams deploy independently without coordinating a release. If you have one team of eight and a deployment that takes twenty minutes, you don't have that problem, and decomposition will cost you more than it returns.
They also assume operational capability that many organisations haven't built. Distributed tracing, meaningful service-level monitoring, automated deployment, and someone who understands what to do when one service is slow rather than down. Without those, extracting services turns a debugging problem you understand into one you don't.
The frequent right answer is a modular monolith: one deployable, with enforced internal boundaries and explicit interfaces between modules. It delivers most of the design benefit at none of the operational cost, and it leaves the door open, because a well-bounded module is straightforward to extract later if the need becomes real.
Where decomposition is warranted, this is how we run it.
Finding the boundaries
Boundaries get derived from the system as it actually is, not from a whiteboard. The analysis covers three things.
Coupling
The call graph and the change history together. Modules that consistently change in the same commit are coupled in a way the code structure may not reveal, and splitting them produces two services that always deploy together.
Data ownership
Which module writes each table, and which only read. Write ownership is usually the strongest available signal for where a boundary belongs.
Team topology
Who owns what today. A boundary that cuts across an existing team's responsibilities creates a coordination cost that outlasts the migration.
Human gate: your architect approves the proposed boundaries and the extraction order before any code moves.
The data problem
Splitting code is the easy half. Splitting a database that several modules read and write is where decompositions stall, and it deserves more space than it usually gets.
The sequence establishes ownership before separation:
- Identify the owning context for each table.
- Route all writes through the owner while readers still read directly. Nothing has moved yet, but write access is now controlled.
- Move readers onto the owner's interface, one caller at a time.
- Separate the data physically, once nothing reaches across the boundary.
Cross-context joins that survive that process need an explicit decision rather than a workaround: usually denormalisation, or an event-carried read model on the consuming side. Transactions spanning what are about to become two services need the same treatment, and the answer is often that the boundary is in the wrong place.
Strangler-fig routing
Extraction happens behind a routing layer. Traffic for a given capability points at the monolith until the replacement service is ready, then shifts, and shifts back if something goes wrong. The monolith stays live throughout and loses responsibilities one at a time.
Each extraction is independently deployable and independently revertible. There's no cutover weekend, and no state where the system only works if every service ships together.
Why Go, when it's Go
Go suits extracted services well: fast start-up, small deployment artefacts, straightforward concurrency and a static binary that makes containers trivial. It isn't automatic. Where a team is deep in one ecosystem and the constraint is throughput rather than familiarity, staying put is often the better call. The target language is a decision at the architecture gate, not a house default.
Observability from the first service
Distributed tracing, structured logging and per-service metrics go in with the first extraction, not once the count reaches double figures. The first time a request crosses three services and gets slow, you'll either have the tooling to see it or you'll be reading logs by hand at two in the morning. That's the moment teams decide the migration was a mistake, and it's avoidable.
In practice
Placeholder — real figures required before launch.
The situation: A monolith where every deploy needed sign-off from several teams.
What we did: Agent-led boundary analysis, then extraction one bounded context at a time with the monolith live throughout.
Outcome: TO SUPPLY: services extracted, months taken, deploy frequency before and after
Questions we get
We'll end up with a distributed monolith.
It's the most likely failure mode and the fear is well founded — you'd keep every coupling and add network calls, partial failure and deployment coordination on top. The defence is that boundaries come from measured coupling and data ownership rather than an org chart, and a proposed boundary that needs synchronous chatter with the service it was split from is evidence the boundary is wrong. Sometimes the honest answer is that two candidate services are one service.
Our database is one giant shared schema.
This is the genuinely hard part and the reason most decompositions stall. Ownership gets established before separation: identify the owning context per table, route writes through the owner, move readers onto its interface, then separate physically. Cross-context joins need an explicit decision. Anyone who tells you this part is quick hasn't done it.
We don't have the ops maturity for thirty services.
Then you shouldn't have thirty, and possibly shouldn't have three. Operational capability is a real constraint on how far to go, not an obstacle to route around. A modular monolith with enforced internal boundaries is often the better first step.
Related capabilities
We'll tell you if you shouldn't do this
An architecture review that concludes "keep the monolith, fix the boundaries" is a legitimate outcome and a cheaper one.