Microservices are a solution to a specific problem: multiple teams needing to deploy independently without coordinating with each other.
That is the problem they solve. Almost every other reason given for adopting them — cleaner boundaries, better scalability, technology flexibility, easier reasoning — is achievable inside a single deployable unit, at a fraction of the operational cost.
This is not an argument that microservices are wrong. It is an argument that the question “should we use microservices?” is usually the wrong question, and the right one is: what are we actually finding difficult?
The costs nobody puts in the estimate
When a distributed architecture is proposed, the estimate usually covers the code. The costs that arrive later:
A function call becomes a network call. It can now be slow, fail, time out, arrive twice, or arrive out of order. Every one of those is a case your code did not previously have to handle, and each needs a decision about retries, idempotency and what the user sees when it fails.
Transactions stop being free. Inside one database, changing two things atomically is a transaction. Across two services, it is a saga, with compensating actions for each failure mode. This is not exotic — it is well understood — but it is a substantial amount of design and testing that a monolith gets for nothing.
Debugging requires infrastructure. “Why was this order rejected?” stops being a stack trace and becomes a correlation-id trace across four services. You need distributed tracing, log aggregation and dashboards before you need them, because the first production incident is a bad time to discover you cannot follow a request.
Local development gets harder. A new engineer who could previously press F5 now needs six services, or a container orchestration setup, or a shared environment they can break for everyone. This tax is paid every day by every developer.
Deployment becomes a matrix. Version compatibility between services is now a thing you manage. Deploying service A before service B may break production, and knowing that requires discipline that has to be built.
None of this is unmanageable. It is simply expensive, and it should be bought deliberately in exchange for something.
What you should be buying
Microservices are worth their cost when at least one of these is true:
Independent deployment by independent teams. Four teams whose release coordination meeting has become the bottleneck. This is the original problem and the strongest justification. Note that it is an organisational condition, not a technical one.
Genuinely divergent scaling. One component needs thirty instances at 9am and one overnight, while the rest is flat. Scaling the whole monolith to serve one hot path is real waste — but check the arithmetic first. Servers are cheap and engineers are not; the crossover point is further away than people assume.
Hard isolation requirements. A component under a compliance regime the rest of the system should stay outside of. Isolation is easier to demonstrate to an auditor at a process boundary.
Genuinely different technology needs. A machine-learning component that must be Python inside an otherwise .NET system. This is legitimate — but it argues for one carved-out service, not for decomposing everything.
Independent failure domains. The reporting module must not be able to take down order capture. Achievable in a monolith with care, but a process boundary makes it structural rather than a matter of discipline.
What you are probably actually experiencing
The reasons teams usually give, and what they generally indicate:
| Symptom | Usually means |
|---|---|
| “The codebase is a mess” | A code-organisation problem. Distributing it makes a mess with network latency. |
| “Deployments are risky” | A testing and pipeline problem. More deployable units makes this worse, not better. |
| “Teams keep stepping on each other” | Ownership boundaries are unclear. Fix the boundaries first — you may find that was the whole problem. |
| “It won’t scale” | Frequently one query, one missing index, or one N+1. Measure before restructuring. |
| “We want to use modern architecture” | Not a requirement. |
That third row is the interesting one, because it is genuinely a real problem and microservices genuinely do solve it. But they solve it by making boundaries physically unavoidable — and if you cannot define the boundaries clearly enough to enforce them in a modular monolith, you will not define them well enough to enforce them across a network either. You will simply discover the mistakes later and more expensively.
The modular monolith
For most business systems, the right target is one deployable unit with strictly enforced internal boundaries:
- Modules own their data. No module reads another’s tables directly.
- Cross-module communication goes through explicit interfaces, not shared internals.
- Boundaries are enforced by tooling — project references, architecture tests in CI — rather than by convention and code review.
- Each module could plausibly become a service later.
That last property is what makes this a low-regret decision. You get most of the clarity of microservices, you keep transactions and stack traces, and you preserve the option to extract any module the day a real reason appears.
And when that day comes, extraction is far easier from a well-modularised monolith than from a badly-modularised one — which is the actual state of most systems that attempt the jump directly.
// Enforcing a boundary in CI is worth more than any amount of
// documentation about the boundary.
[Fact]
public void Ordering_module_does_not_reference_Billing_internals()
{
var result = Types.InAssembly(typeof(OrderService).Assembly)
.That().ResideInNamespace("WeInDev.Ordering")
.ShouldNot().HaveDependencyOn("WeInDev.Billing.Internal")
.GetResult();
Assert.True(result.IsSuccessful, string.Join(", ", result.FailingTypeNames ?? []));
}
A decision sequence
- What is difficult right now? Name it specifically. If you cannot, this is a fashion decision.
- Is it an organisational problem? Multiple teams blocked on each other is a genuine microservices problem. One team is not.
- Have you measured? Scaling and performance claims should have numbers. Restructuring is an expensive way to find out it was a missing index.
- Can you draw the boundaries today? If not, draw them inside the monolith first. That work is required either way and is reversible.
- Can you operate it? Tracing, aggregated logging, deployment orchestration and on-call have to exist before the first service ships, not after.
- What is the smallest step? Usually: extract one component with a genuine reason. Live with it for a quarter. Then decide about the next one.
Almost nobody regrets extracting one service. Plenty of people regret extracting twelve at once.
The uncomfortable summary
If you have one team, one deployment pipeline, no distributed-systems experience on staff, and a system that is hard to change — microservices will make everything worse, and the difficulty you have will still be there, now with a network in the middle of it.
Fix the boundaries. Fix the deployment pipeline. Fix the tests. Then, if the organisational reason appears, extract deliberately and one at a time.
The goal was never the architecture. It was being able to change the system safely, and there are cheaper routes to that than a distributed one.
Written by WeInDev. If this is a decision you are facing now, it is also a conversation we are happy to have — thirty minutes, no pitch deck.