You Probably Don't Need Microservices Yet
by Eric Hanson, Backend Developer at Clean Systems Consulting
The question you should ask before you split anything
Your engineers are arguing about microservices in your architecture review. Someone has drawn boxes on a whiteboard and labelled them "User Service," "Order Service," and "Notification Service." The argument is framed as: monolith versus microservices, old versus modern, slow versus fast. That framing is wrong, and it's leading you toward a decision that will cost you twelve to eighteen months of platform work you didn't plan for.
The right question is not "should we use microservices?" It's "what specific problem are we trying to solve, and is microservices the minimum intervention required to solve it?"
What microservices actually solve
Microservices solve three problems well:
Deployment independence at team scale. When you have multiple teams working on the same codebase and they're blocking each other's releases, a shared deployment artifact becomes a coordination bottleneck. Splitting into services lets teams deploy on their own schedule without coordinating with every other team. This matters when you have enough teams. With two or three teams, the coordination overhead of microservices (shared libraries, contract tests, versioned APIs) exceeds the coordination cost of scheduled shared deployments.
Independent scaling. If your image processing workload needs 40 CPU cores and your user authentication needs 2, running them in the same process means either over-provisioning auth or under-provisioning image processing. Separate services let you scale independently. This is a real benefit — but it requires that your scaling profiles actually differ meaningfully. If everything scales together because everything is I/O-bound on the same database, you gain nothing.
Technology isolation. If one part of your system legitimately benefits from a different runtime — say, a Python ML inference service alongside a Java API — separate services let you do that. But "we might want to switch languages someday" is not a reason to pay the microservices tax today.
Notice what's not on that list: code organization, development velocity for small teams, or making the system easier to understand. Microservices don't improve any of those things. They usually make them worse.
What a well-structured monolith can do instead
A modular monolith — a single deployable artifact with clearly enforced internal module boundaries — gives you most of the development benefits of microservices without the operational complexity.
In Java, this means packages that don't cross-import. In a Node.js project, it means bounded module directories with explicit public API surfaces. You can enforce these with ArchUnit (Java) or dependency-cruiser (JavaScript) in CI:
// ArchUnit rule: orders package cannot import from payments internals
noClasses().that().resideInAPackage("..orders..")
.should().dependOnClassesThat()
.resideInAPackage("..payments.internal..");
This pattern gives you independent development velocity — teams work in their own modules without stepping on each other — while keeping deployment simple, transactions trivially consistent, and observability cheap.
When a module genuinely needs to scale independently or be deployed independently, you can extract it then. You will have clean boundaries already drawn. The extraction becomes a structural refactor rather than an architecture reimagining.
The signals that mean you actually need to split
You need microservices when you can check at least two of these:
- You have four or more teams deploying to the same codebase and release coordination is measurably slowing delivery.
- You have workloads with radically different scaling characteristics that are causing resource contention.
- One part of the system has significantly different availability requirements and you need to fault-isolate it.
- You have a compliance or security requirement that mandates data isolation between components.
If you can't check two of those, you don't have a microservices problem. You might have a code quality problem, a team process problem, or a database schema problem — none of which microservices fix.
The cost you're not accounting for
Teams that adopt microservices prematurely consistently underestimate the operational platform you need to make them work:
- Centralized logging with correlation ID propagation across services
- Distributed tracing (OpenTelemetry + Jaeger or a managed equivalent)
- Service-to-service authentication (mTLS or a service mesh like Istio/Linkerd)
- Health check standards and dependency-aware readiness probes
- Contract testing in CI (Pact or equivalent)
- A saga or outbox pattern for cross-service data consistency
That list is six to twelve months of platform work for most teams, on top of the migration itself. Teams that skip it end up with microservices that are harder to debug than the monolith was, with none of the independence benefits because everything is still coupled through synchronous REST calls.
Before you draw another architecture box on that whiteboard, spend one sprint auditing your current monolith. Define the module boundaries you wish you had. Enforce them. If that causes pain, you've learned something specific about what needs to change. If it doesn't cause pain, you may have already solved the problem without adding a distributed system to your operational burden.