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.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

Why MVC Is Not Enough for Complex Backend Systems

MVC is great for small apps, but when your backend starts juggling caching, queues, and multiple APIs, it quickly shows its limits.

Read more

Designing with Java Enums — When They're the Right Model and When They're Not

Java enums are more capable than most developers use them for, but that capability has limits. Here is a clear-eyed look at what enums do well, where they break down, and the design decisions that determine which side you end up on.

Read more

The Head Chef Analogy: Why Teams Without a Tech Lead Fail

Imagine walking into a busy kitchen with 10 cooks and no head chef. Food is being made—but no one agrees on how it should taste.

Read more

Your Transactions Are Bigger Than They Need to Be

Oversized transactions are one of the most common sources of lock contention, replication lag, and autovacuum interference in production databases — and they are almost always fixable without changing business logic.

Read more