The microservices fashion peaked in 2018 and quietly cooled. By 2026 the consensus is: most products start as a modular monolith and split when they have specific reasons to. This post is the pragmatic guide.
Why monolith first
- Faster to build. One deploy, one DB, one repo.
- Simpler to debug. Stack traces don’t span network.
- Easier to refactor. Move code freely.
- Cheaper to operate. One process, fewer moving parts.
For a team of 1–10 engineers shipping a product, monolith wins almost always.
Modular monolith — the right starting shape
app/
├── modules/
│ ├── orders/
│ │ ├── api/ # routes
│ │ ├── service/ # business logic
│ │ ├── repo/ # DB access
│ │ └── domain/ # types
│ ├── billing/
│ ├── inventory/
│ └── notifications/
├── shared/ # only what truly cross-cuts
└── main.py
Rules:
- Modules talk via well-defined interfaces, not raw DB access.
- One module’s data is opaque to others — request via service.
- No cross-module DB joins.
- Shared code is minimal.
Result: each module has the boundary of a microservice but lives in one process. Splitting later is a refactor, not a rewrite.
When to split
Real reasons to split a monolith:
- Team independence. Different teams own different services; deploy independence matters.
- Resource asymmetry. ML inference needs GPUs; the rest doesn’t. Split to scale appropriately.
- Different runtime needs. One module is Rust for perf; rest is Python.
- Compliance. A subset of code touches PCI / HIPAA data; isolate to limit scope.
- Real scale. One module’s load profile diverges from the rest.
Bad reasons:
- “We’re growing.” Monolith scales further than people think.
- “We saw a talk about microservices.”
- “Resume-driven development.”
The cost of microservices
Real cost:
- Each service needs its own pipeline, deployment, observability, on-call.
- Network calls fail; you need retry / circuit breaker .
- Distributed transactions impossible; need outbox / saga .
- Debugging spans multiple services; need distributed tracing .
- Operational headcount grows.
A 5-service monolith → 5 microservices is a 5× operational burden, often without commensurate benefit.
How to split well
When the moment comes:
- Pick the boundary. A module that’s already cleanly separated.
- Establish the contract. API spec; tests against it.
- Run both for a phase — same code, deployed monolith and as a service.
- Cut traffic over to the service.
- Remove the module from the monolith.
The modular monolith made this incremental.
Read this next
- Distributed Systems Fundamentals
- Idempotency, Retries, and Exactly-Once Illusions
- Circuit Breakers, Bulkheads, and Backpressure
- Platform Engineering and IDPs
If you want a modular-monolith template (FastAPI / Hono / Axum flavors), it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .