What you will take away
- Microservices solve an organisational problem before they solve a technical one.
- A modular monolith gets most of the benefit at a fraction of the operational cost.
- Split along data ownership - a service that shares a database is not a service.
What microservices actually buy you
The genuine benefit is independent deployment. Teams ship on their own schedule without coordinating a release train, a failure in one area degrades rather than stops the product, and you can scale the expensive path without scaling everything around it.
Notice that two of those three are about people. Microservices are a response to the coordination cost of many teams touching one codebase. With three engineers that cost does not exist, and you have bought the overhead without the problem it solves.
What they cost
- Every in-process call becomes a network call that can fail, time out or arrive twice.
- Transactions across services need sagas or compensation, which are far harder to reason about than a database transaction.
- Debugging requires distributed tracing, because a stack trace no longer spans the request.
- Local development needs a way to run or fake a dozen services.
- Every service needs its own pipeline, monitoring, alerting, on-call rota and dependency upkeep.
None of these is insurmountable. All of them are ongoing, and all of them are paid by the same engineers who were supposed to be building features.
The modular monolith is usually the right answer
A well-structured monolith with hard internal boundaries - modules that talk through defined interfaces, each owning its own tables, none reaching across into another module's data - gives you most of the design benefit and none of the operational tax. It deploys as one unit, debugs with one stack trace, and runs on one machine in development.
If you cannot draw clean module boundaries inside one codebase, splitting it across the network will not create them. It will just make the mess harder to see.
It also leaves the door open. When one module genuinely needs independent scaling or a separate release cadence, a clean boundary is exactly what makes extracting it a contained piece of work.
Signals that it is time to split
Split when the pain is specific and measurable: releases are blocked because unrelated teams must coordinate, one component needs ten times the resources of the rest, a section has a materially different availability requirement, or a regulated domain needs its own data boundary.
Do not split because the architecture diagram looks more modern, because a conference talk described it, or because you expect traffic that has not arrived. Speculative decomposition is how teams end up operating a distributed system to serve a thousand users a day.
How to split without making it worse
Cut along data ownership, not along technical layers. A service that reads another service's tables is not a service; it is a deployment unit with hidden coupling, and it will fail in ways that are extremely hard to trace. Each service owns its data and exposes it only through its interface.
Extract one boundary at a time, with traffic actually moved and the old path removed before starting the next. Add tracing before the first extraction, not after the first incident. And accept eventual consistency deliberately, deciding for each flow what the user sees while the system catches up.