← Blog
Foundations

My Trip to "Microservices Hell" (Why I Often Take the Monolith Instead)

Thien Nguyen · Nov 6, 2025

As an architect, I've watched plenty of teams jump into microservices with excitement, sold on the dream of "infinite scale" and "every team owns its own thing."

And I've watched those same teams a year later, drowning in complexity, wondering why shipping one new feature now takes six weeks.

"Microservices Hell" is real, and the rent there isn't cheap.

It's the state you fall into when the engineering plumbing becomes orders of magnitude more complex than the very business logic it was supposed to serve.

Based on what I've lived through myself, here's an honest picture of that descent into hell.


1. The headache called "Eventual Consistency" (or, the death of ACID)

The first thing that hits you in the face is the database.

I remember one project with an absolutely critical flow: Create orderUpdate inventoryProcess payment.

In a monolith, this is just a single database transaction, clean and elegant. It's atomic. It's safe. And it just works.

In microservices, it's now 3 services, most likely with 3 separate databases. You can't have a single transaction anymore.

You're forced to hand-write extra compensating logic (people give it the fancy name Saga, but at its core it's just "code to clean up the mess left by other code").

This "compensating logic" is a massive bug nest. You now live in the land of "eventual consistency," a polite way of saying:

"Your data is currently wrong, but we'll probably manage to fix it… eventually."

For any FinTech or HealthTech system, this is unacceptable.

(Plenty of teams "cheat" by sharing a single database. Don't do it. You've just created a hidden monster, a single point of failure for the entire system.)


2. The network tax (or, "My function call is now a bug")

When you trade reliable in-memory function calls for flaky HTTP calls, you pay a very heavy tax.

Every engineer on your team, like it or not, now has to become a distributed-systems expert.

Every. Single. Call. now has to handle:

Timeout: What happens when UserService simply… can't be bothered to respond?

Retry: If you call again, is that request idempotent? Congratulations, you just charged your customer twice.

Circuit Breaker: You're required to implement this so that a dead service (InventoryService) doesn't drag every other service that calls it down with it.

Cognitive load skyrockets.

I've seen teams of 10 engineers struggling to carry 40 services. You've just rebuilt your exact same old monolith, except this time it's wired together over a sluggish network.


3. The observability tax

This is the part that really kills your speed. Remember back when you had just one single log file? Those were the good old days.

Now, a single user click can pass through 10 different services. When it breaks, you're not debugging anymore; you're playing a distributed-scale game of whodunit Monopoly.

Before you can even write a single line of feature code, you've already had to pay the "observability tax":

Distributed Tracing (e.g. Jaeger)

Centralized Logging (e.g. ELK Stack)

Metrics Aggregation (e.g. Prometheus)

And this isn't just a production problem; it also wrecks your development environment.

How are you supposed to run 40 services on a single engineer's laptop? How do you even do E2E testing?


4. The people and management hell

But the worst tax of all isn't technical. It's the people tax.

The engineer-to-service ratio spirals out of control: I've seen each engineer owning 4-5 services. This isn't "autonomy"; this is "burnout." One person is now the operator, the debugger, and the on-call for half a dozen systems at once.

"Resume-Driven Development": When "autonomy" turns into "anarchy," you end up with one service written in Kotlin, one in Go, and one in Rust that exactly one person understands. The day that person quits, you've "orphaned" a piece of your own system.

Architecture mirrors the org chart: This is fine… until the company reorganizes. And companies reorganize all the time. Suddenly the "Payments" team gets split up, but your entire infrastructure is tangled in knots. You've just voluntarily signed up for a migration project that delivers zero value to the customer.


So… when is the "boring" monolith (done right) actually far better?

A well-designed Modular Monolith (cleanly separated modules sharing a single codebase) is by no means a "relic." It's a pragmatic choice, and often the superior one.

In my experience, the monolith wins hands down in the following cases:

1. When transactional integrity (ACID) is king: If you're building FinTech, HealthTech, or a complex ERP system, your business requires 100% consistency. The reliability of a true database transaction is non-negotiable.

2. When you're an early-stage product (time-to-market is king): Your biggest risk isn't scale; it's building the wrong thing. A Modular Monolith lets you move astonishingly fast. Refactoring inside a monolith is 100 times easier than refactoring 10 microservices.

3. When you're a small-to-medium team (1-20 engineers): Microservices are a tool for solving the people-scaling problem. If you're just one team, microservices will kill your speed.

4. When you don't yet have a dedicated Platform team: Choosing microservices without an SRE/Platform team is like buying a Formula 1 race car to run grocery errands. Expensive, hard to drive, and you're going to crash it one way or another.


My final piece of advice

Start with a Modular Monolith.

Design it with clear boundaries, communicate through interfaces, and absolutely never share database tables between modules.

This gives you 90% of the benefit of microservices (the separation) at just 10% of the operational cost.

Only split a module out into its own microservice when you have a clear, painful, and obvious reason.

Microservices are a refactoring step, not a starting point.

Modular monolith vs microservices: a side-by-side

If you only remember one thing: most teams reaching for microservices actually want a modular monolith — strong module boundaries inside a single deployable. Here is how the two compare on the things that bite you in production.

DimensionModular monolithMicroservices
Data consistencyOne database, real ACID transactionsDistributed data, eventual consistency, sagas
A "function call"In-process, microseconds, type-checkedA network hop: latency, retries, partial failure
DebuggingOne stack traceDistributed tracing across N services
DeploymentOne pipelineN pipelines, versioning, API contracts
Team costOne team can hold it in their headNeeds real platform / DevOps maturity
Independent scalingScale the whole appScale just the hot service
Right when…Almost always at the start, and well past PMFReal scale pain on a specific, identifiable module

The modular monolith gives you roughly 90% of the architectural benefit (clean separation) at about 10% of the operational cost. You keep the option to split a module out later — but you pay the distributed-systems tax only when a specific module genuinely forces your hand.

When should you actually use microservices?

When a specific module has a clearly different scaling profile, must be deployed independently by a separate team, or needs hard isolation for security or compliance — and the pain of keeping it in the monolith is concrete, not hypothetical. Treat splitting a service out as a refactoring step you earn, never a starting point. If you are weighing this exact call, it is the kind of architecture decision we help founders get right — see what we do and the systems we have shipped, or talk to us before you commit to a topology you will pay for every day.

What is "microservices hell"?

It is the operational pain that shows up when a system is split into many services before the team needs it: lost ACID guarantees and eventual-consistency bugs, network latency and partial failures where a function call used to be, distributed-tracing overhead just to debug, and the coordination cost of many pipelines and teams. The "hell" is that all of it is the cost of the architecture, not of your actual product.

Is a modular monolith better than microservices?

For most teams, especially before and around product-market fit, yes. A modular monolith gives you clean module boundaries and most of the design benefit at a fraction of the operational cost, while keeping the door open to extract a service later. Microservices win only when a specific module has a real, identified reason to be independent.

What is the "microservices tax"?

The recurring, often hidden costs microservices add regardless of feature work: the network tax (latency, retries and serialization on every cross-service call), the observability tax (distributed tracing, log aggregation and correlation just to see what happened), and the people tax (more pipelines, contracts and coordination). Pay it only when a module's benefit clearly outweighs it.


Discover more in-depth analysis and technology trends at digitalam.org.