When NOT to Use Microservices: A Decision Framework
Microservices solve specific problems. Adopting them without the prerequisites doubles your operational cost and slows delivery. Here is when to say no.
- Author
- Randhir Jassal
- Published
- Reading time
- 9 min read
The five honest preconditions
Before any "should we go microservices?" conversation, check that all five are true. Missing any one is a signal to keep the monolith.
- Multiple teams of 8+ engineers with non-overlapping ownership
- Independent deploy cadences — different release windows or freeze rules
- Heterogeneous tech stacks justified by the workload (ML in Python, real-time in Go, transactional in .NET)
- An operational platform — observability, service mesh, secret management — already paid for
- Customer-visible scaling pain in a specific subsystem that the monolith can't service-isolate
If you're a 6-person team without any of these, microservices will cost you 30–50% of your delivery throughput. Don't.
What replaces microservices for most teams
Modular monolith. One process, one deploy, but enforced module boundaries.
/src
/Identity
Domain/
Application/
Infrastructure/
IdentityModule.cs ← only public entry point
/Billing
Domain/
Application/
Infrastructure/
BillingModule.cs ← only public entry point
/Notifications
...
ArchUnit-style tests enforce that Billing never imports from Identity.Domain — only from IdentityModule. You get module isolation without the network.
[Fact]
public void Billing_should_not_depend_on_Identity_internals() {
var billing = Types.InAssembly(typeof(BillingModule).Assembly);
var result = billing
.Should()
.NotHaveDependencyOn("Identity.Domain")
.GetResult();
Assert.True(result.IsSuccessful);
}
The hidden costs people miss
- Network is not a method call. Every cross-service interaction is a possible timeout, retry, partial failure, and serialization cost.
- Distributed transactions don't exist the way you want them to. You'll write the saga pattern by hand and your eventual consistency window will leak into the UX.
- Local-dev story degrades. Spinning up 15 services to run a feature branch is friction every engineer pays every day.
- Debugging requires distributed tracing. Without it you're flying blind. With it, you're paying for an observability vendor.
When microservices are worth it
- Subsystem with fundamentally different scaling envelope (e.g. an image-processing pipeline that scales 100x on traffic spikes; the rest of the app stays at 1x)
- Regulatory isolation (e.g. a payments service that has to be PCI-DSS scoped separately from the rest)
- Replace-the-engine refactor where you're carving off a piece written in a sunset language
Otherwise, the modular monolith you already have can probably hold another 10x. Most teams move to microservices too early and pay a tax they can't measure until they're inside it.
The pragmatic middle path
- Build the modular monolith
- Add observability you'd need for microservices anyway (OpenTelemetry, structured logs)
- When a specific module's scaling envelope diverges, extract that one module — not all of them
You end up with 2-3 services, not 30. The math works.
Get the next issue
A short, curated email with the newest posts and questions.