When should you not use microservices?
Most systems that get split should not have been. Microservices solve an organisational problem, not a technical one, and if you do not have that organisational problem you pay the entire cost and collect none of the benefit. The useful version of this question is narrow: what specifically is blocked today, and would a network boundary unblock it.
When are microservices the wrong answer?
Three conditions each independently disqualify a split. One team owns the whole system, so nobody is waiting on anybody. The domain is still changing shape, so the boundaries you draw this quarter will be wrong next quarter. Or the proposed services would keep sharing one database, which means you have added network calls without gaining independence.
The reason these matter is that the benefit of a microservice is the ability to deploy it without coordinating. A single team does not need that, because coordination is a conversation rather than a release train. A changing domain cannot have it, because every feature crosses the boundary you just drew and now needs two deploys instead of one.
The shared database case is the most common and the least honest, because it is usually described as a first step. It is not a first step. It is the finished state of most splits that start that way, and it produces a system with the failure modes of distribution and the coupling of a monolith.
What do microservices actually solve?
Four things, all organisational or operational. Independent deployment, so a team ships without waiting for another team's release. Independent scaling, when one part of the workload genuinely has a different shape from the rest. Runtime isolation, when a component needs a different language, a GPU, or a memory profile the rest of the system should not carry. And blast radius, so a slow component degrades one feature instead of exhausting the shared thread pool.
Notice what is not on that list. Microservices do not make code cleaner: module boundaries do that, and they work inside one process. They do not make a system faster, and usually make it slower, because a function call becomes a serialised round trip. They do not reduce complexity; they relocate it from the codebase into the network, where it is harder to see and harder to test.
The practical filter is to name which of the four you are buying, in a sentence, with the team or workload it applies to. If the sentence is vague, the split is being driven by fashion or by a hiring plan rather than by a constraint.
What changes when a call becomes a network call?
Everything about how it fails. An in-process call either returns or throws, and it does so in nanoseconds with the same view of memory. A network call can also time out, return late after the caller gave up, succeed on the server while failing at the client, or succeed twice. Every one of those is now a case your code must handle explicitly, and each is a case your tests must produce deliberately.
| Concern | In-process call | Cross-service call |
|---|---|---|
| Latency | Nanoseconds, predictable | Milliseconds, tail-heavy under load |
| Failure modes | Return or throw | Timeout, partial success, duplicate, late reply |
| Refactoring | Compiler finds every caller | Callers are found in production |
| Consistency | One transaction, rolls back | No transaction, needs compensation |
| Debugging | One stack trace | Correlated traces across services, if you built them |
| Version change | Deploy together | Both versions live at once, indefinitely |
Is this a scaling problem or a coupling problem?
Almost always coupling, described as scaling. A system that is slow under load usually has one endpoint, one query or one lock causing it, and splitting the codebase does nothing to that query. Find the actual bottleneck before deciding the shape of the architecture, because the answer frequently turns out to be an index, a cache, or a synchronous call that should have been a background job.
Where scaling genuinely argues for a split is when two parts of the workload have different shapes: a bulk import that wants many cores for ten minutes a day sitting next to a request path that wants low latency all day. In one process they compete, and the import wins because it is greedy. That is a real reason, and it is narrower than the general case.
Otherwise, run more copies of the whole application. A stateless monolith scales horizontally exactly as well as a microservice does, and the copies are identical, which makes capacity planning and rollback simpler rather than harder.
What breaks first when a small team splits too early?
Local development, then incident response, in that order. Running the system on a laptop starts to mean starting eight containers and a message broker, so people stop doing it and start testing in a shared environment where their changes collide with everyone else's. That single change quietly slows the team more than the split ever sped it up.
Then the first serious incident arrives and spans four repositories. Without distributed tracing already in place, the investigation becomes a sequence of guesses, and the mean time to understand what happened grows in proportion to the number of services rather than the size of the problem.
The end state has a name worth using in planning meetings: a distributed monolith. Services that must be deployed together, that share a database, and that fail together, but which pay network latency and operational overhead for the privilege. It is strictly worse than the monolith it replaced, and it is the most common outcome of an early split.
What test can you run this afternoon?
Open the last fifty merged pull requests and count how many touched more than one area of the codebase. If most changes are confined to one area, you have natural boundaries and could split them later with little pain, which also means you are not currently suffering. If most changes cross areas, splitting those areas turns each of those changes into a multi-repository, multi-deploy coordination exercise.
Then check the deploy log for the other half of the answer. Count how many times in the last quarter a team was actually blocked from releasing by someone else's unfinished work. If the number is near zero, independent deployment is not a benefit you can currently spend.
Both numbers take under an hour to gather and they are more informative than any architectural review, because they measure what your organisation does rather than what it intends to do.
Common questions
- When should you not use microservices?
- When one team owns the whole system, when the domain is still changing shape, or when the proposed services would continue sharing a database. The benefit of a microservice is deploying without coordinating with another team, so a single team gains nothing. A changing domain means features keep crossing the new boundary. A shared database removes independence while keeping every cost of distribution.
- Do microservices improve performance?
- Usually the opposite. A function call that took nanoseconds becomes a serialised network round trip measured in milliseconds, with tail latency that grows under load. Microservices can help when two parts of a workload have genuinely different shapes, such as a bulk job competing with a low-latency request path, but general throughput is better addressed by running more copies of the whole application.
- What is a distributed monolith?
- A set of services that must be deployed together, share a database, and fail together, while still paying the latency and operational cost of being separate processes. It is the usual result of splitting a system before the boundaries were clear, and it is worse than the monolith it replaced because it adds network failure modes without adding independence.
- How many engineers do you need before microservices make sense?
- Team count matters more than headcount. The question is whether there are multiple teams whose release schedules actually collide, and the honest way to check is to count how many times in the last quarter a team was blocked from shipping by another team's unfinished work. If that number is near zero, independent deployment is a benefit you cannot yet spend.
- Can you start with a shared database and split it later?
- It is possible but rarely happens, because once several services read the same tables, every schema change requires coordinating all of them and the incentive to finish the split disappears. Teams in this state have the coupling of a monolith plus the failure modes of a distributed system. If the data split is not planned from the start, plan for it to remain unfinished.