I've seen this happen on more projects than I can count.
The codebase grows, people start stepping on each other's toes, and then someone in a planning meeting says it:
"We should move to microservices."
A week later the architecture diagram has an API Gateway, Kafka, Redis, five databases, Docker, Kubernetes, and ten brand new services sitting in it.
It looks great in a slide deck. Nobody asked the actual question though:
What problem are we solving?
Microservices are genuinely useful. But these days people reach for them because they look modern, not because anyone stopped to check if the team actually needs them.
What These Words Actually Mean
A monolith is one application. Users, products, orders, payments, notifications, analytics — all of it lives in the same codebase, and any part can call any other part directly.
ONE APPLICATION
┌─────────────────────────────────────┐
│ Users Products Orders Payments │
│ Notifications Analytics │
└─────────────────────────────────────┘
For a small team this is usually the right call. It's fast to build, easy to reason about, nothing to coordinate. Where it goes wrong is when the codebase keeps growing but nobody adds any structure — everything reaching into everything else, no clear owner for anything.
A modular monolith solves that without touching the network at all. Still one deployable app, but the code is split into modules with real boundaries between them.
APPLICATION
│
┌─────────────────┼─────────────────┐
↓ ↓ ↓
User Module Order Module Payment Module
Each module owns its own logic, and other modules talk to it through a defined interface instead of just reaching in. You get the organization without inheriting a distributed system. Honestly, most apps should just stay here — for years, sometimes forever.
Microservices actually split the app into separate services that talk to each other over a network.
API Gateway
│
┌──────────────┼──────────────┐
↓ ↓ ↓
User Service Order Service Payment Service
│ │ │
DB DB DB
Each piece can now deploy and scale on its own. But a function call just became a network call, and that changes everything about how the system can break.
People Look at the Wrong Example
Everyone points at Amazon, Netflix, Uber and thinks: "they're huge and successful and they use microservices, so that's clearly the move."
That's backwards. Those companies didn't get big because of microservices. They got big, ran into real scaling and org problems, and microservices happened to be one way to deal with it. Thousands of engineers can't all push to the same codebase without constantly blocking each other. Search traffic and checkout traffic don't behave the same way, so scaling them together makes no sense.
If your team doesn't have those problems yet, none of that applies to you. And that's completely fine.
The Part That Doesn't Show Up on the Diagram
A function call like this:
paymentService.process(order)
turns into this once it's a separate service:
Order Service → Network → Payment Service → Database
Now you've got real questions to answer. What happens if the network drops mid-request? What if Payment Service is just down? What if it takes ten seconds to respond? What if it actually succeeds but you never get the response back?
So you end up building retries, timeouts, tracing, message queues, idempotency handling, failure recovery — all before you've even shipped the feature you set out to build.
You didn't make the system simpler. You swapped one complicated app for twenty smaller ones, all talking to each other over a network that will occasionally fail.
A Better Question to Ask First
Instead of "should we use microservices," try asking what's actually broken.
- Code is messy → that's a modularity problem, not an infrastructure one.
- App is slow → look at your queries, your indexes, your caching before you touch the architecture.
- 20-person team keeps blocking each other → a modular monolith usually fixes this.
- Different teams genuinely need to deploy and scale completely different domains on their own → now microservices start to make sense.
The architecture should come from the problem. Not the other way around.
What Maturity Actually Looks Like
It's not "we use microservices." It's being able to explain why you picked what you picked.
"We're staying a modular monolith because the team's small and our domain boundaries are still moving" — that's a mature call.
"We pulled Payments out because it needs different scaling, a different on-call team, and stricter reliability guarantees" — that's also a mature call.
Same level of maturity, completely different tech. What matters is the reasoning, not what's on the diagram.
A Concrete Example
Say you're running an e-commerce app as a modular monolith, traffic's growing, and you check your numbers:
Search requests: 10 million/hour
Checkout requests: 1 million/hour
Admin requests: 10,000/hour
Scaling the whole app to handle Search's load means you're also scaling Checkout and Admin along with it, for no reason. That's wasted infrastructure.
This is a real case for pulling Search out on its own:
Search Service → 50 instances (heavy read traffic, needs to scale on its own)
Checkout Service → 10 instances (moderate traffic, business-critical)
Admin Service → 2 instances (low traffic, internal only)
What makes this a good decision isn't "microservices are the modern way to build things." It's a specific, measurable bottleneck — one part of the system needed to scale completely differently from the rest — and pulling it out fixed exactly that, without touching anything else.
Final Thought
Microservices aren't bad. Monoliths aren't outdated. A modular monolith isn't some starter architecture you're supposed to graduate out of.
They're just different tools for different problems. The mistake isn't choosing microservices — it's choosing them because "that's what modern companies do," without ever asking if your team has actually outgrown what you've got.
One Way to Picture It
If all that still feels a bit abstract, here's a way to make it click: think of it like a company growing over time.
A 5–10 person startup works out of one open room — devs, HR, finance, sales, all a few feet from each other. Need something from finance? Just turn around and ask. That's a monolith: everything's in one place, any part can reach any other part.
Grow to 200 people and you don't need 200 buildings, you need walls. Departments get their own space inside the same building — HR here, Engineering there — each with clear ownership, still under one roof. That's a modular monolith: one app, real internal boundaries.
It's only once a department gets big enough to need its own building — its own security, its own infrastructure, its own way of running things — that it makes sense to move it out and connect it back with roads and phone lines instead of a hallway. That's microservices: real independence, but now you need real infrastructure to hold it together.
| Architecture | Company Analogy | What It Means in Code |
|---|---|---|
| Monolith | Everyone works in one room | One tightly connected application |
| Modular Monolith | One building, separate departments | One app, clear internal boundaries |
| Microservices | Separate buildings for each department | Independent services over a network |
Don't put up a new building because it looks good on a diagram. Put one up when you've actually outgrown the one you're in.
Top comments (0)