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.
The first thing that hits you in the face is the database.
I remember one project with an absolutely critical flow: Create order → Update inventory → Process 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.)
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.
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?
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.
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.
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.
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.
| Dimension | Modular monolith | Microservices |
|---|---|---|
| Data consistency | One database, real ACID transactions | Distributed data, eventual consistency, sagas |
| A "function call" | In-process, microseconds, type-checked | A network hop: latency, retries, partial failure |
| Debugging | One stack trace | Distributed tracing across N services |
| Deployment | One pipeline | N pipelines, versioning, API contracts |
| Team cost | One team can hold it in their head | Needs real platform / DevOps maturity |
| Independent scaling | Scale the whole app | Scale just the hot service |
| Right when… | Almost always at the start, and well past PMF | Real 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 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.
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.
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.
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.