Architecture
When microservices are the wrong answer
Microservices solve an organisational problem, not a technical one. If you have fewer than about twenty engineers, they usually cost more than they return.
Most teams that adopt microservices do it for a technical reason and get an organisational bill.
The technical pitch is familiar: independent scaling, fault isolation, freedom to pick the right language per service. All real. All available to a well-structured monolith too, at a fraction of the operational cost.
What microservices actually solve is a coordination problem. When you have enough engineers that merge conflicts, release coordination and deployment queues become the bottleneck, splitting the deployable unit lets teams move without waiting for each other. That is a genuine and important benefit — and it only pays once you have that many teams.
The bill you receive on day one
Before any of the benefits arrive, the costs do.
A network appears in the middle of your logic. A function call that could not fail now can. Every service boundary needs timeouts, retries with backoff, and idempotency on the receiving side, because a retried call must not double-apply. That is real code, and it exists in every service.
Transactions stop working. A single database transaction across two tables becomes a distributed workflow across two services. You are now implementing sagas and compensating actions for something the database used to do correctly for free.
Debugging requires infrastructure. A stack trace no longer tells you what happened. You need distributed tracing before you can answer "why was this request slow", and you need it working before the first incident, not after.
Local development gets worse. A new engineer cannot run the system on their laptop without either a heavy orchestration setup or a set of mocks that drift from reality.
None of this is a reason never to do it. It is the reason to be honest about the timing.
The threshold, roughly
The number we use is about twenty engineers, or the point where you have three or more teams that genuinely need to release on independent schedules. Below that, the coordination pain that microservices relieve does not exist yet, so you are paying operational cost for a benefit you cannot collect.
Below that threshold there is almost always a better move: fix the module boundaries inside the monolith.
What to do instead
A modular monolith gets you most of the way, and it makes the eventual split cheap rather than speculative.
- Enforce boundaries in code. Separate modules with explicit public interfaces, and a lint rule or build check that fails if one module reaches into another's internals. This is the actual work of a service split, done without the network.
- Give each module its own schema. Same database instance, separate schemas, no cross-schema joins. When you later extract a module, its data comes with it instead of needing to be untangled first.
- Communicate through an in-process event bus. Publishing an event to a local bus and publishing to a queue look nearly identical at the call site. Swapping the transport later is a small change; introducing the concept later is not.
- Deploy one artefact. One build, one deploy, one rollback. Keep this for as long as it is not the thing slowing you down.
Do this and the extraction decision becomes empirical. When one module genuinely needs different scaling or a different release cadence, it lifts out along a boundary you already enforced — and you extract exactly that one, not all fifteen.
The question worth asking
Not "are microservices better?" — that question has no answer without context. Ask instead: what is currently slowing us down?
If the honest answer is "our release process requires four teams to coordinate," microservices are pointed at your actual problem. If the answer is "we think we might need to scale later," you are buying insurance whose premium is paid in every future debugging session.
The systems we have seen struggle most were not monoliths that should have been split. They were eight services built by five engineers, where every feature touched four of them, and nobody could run the thing locally.