Monolith vs Modular Monolith vs Microservices#
Three architectural styles for the same application. Each makes a different bet on team scale, operational maturity, and rate of change.
Side-by-side#
| Monolith | Modular monolith | Microservices | |
|---|---|---|---|
| Deployment unit | One artifact | One artifact, many modules | Many services, deployed independently |
| Process boundary | One process | One process | One process per service |
| Database | One shared DB | One DB, sometimes split per module | One DB per service (typically) |
| Inter-module calls | In-process function call | In-process function call across modules | Network call (gRPC / REST / events) |
| Team coordination | Tight (merge conflicts, release train) | Module owners, shared deploy | Loose (independent CI/CD) |
| Cross-cutting changes | One PR | One PR | Many PRs, often versioned |
| Operational complexity | Low | Low | High (mesh, observability, deploys × N) |
| Latency between modules | nanoseconds | nanoseconds | milliseconds (network + serialize) |
| Failure isolation | None (one crash = all down) | Limited | High (cell + circuit-breaker) |
| Best for | Startups, MVPs, small teams | Most companies < 100 engineers | Many teams, independent deploy cadence, polyglot |
Decision tree#
flowchart TB
Q[Building a service]
Q --> A{Team size > 50<br/>engineers?}
A -->|no| MM[Modular monolith<br/>cleanest start]
A -->|yes| B{Polyglot or<br/>independent deploys mandatory?}
B -->|yes| MS[Microservices]
B -->|no| MM
Q --> C{Pure MVP<br/>1-3 engineers?}
C -->|yes| MO[Monolith<br/>ship today]
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class Q,A,B,C service;
class MO,MM,MS datastore;
What goes wrong#
Premature microservices#
- Distributed monolith: services that must deploy together — gain all the network cost, none of the isolation.
- No platform team → every service team reinvents observability / CI / mesh.
- Latency blows up: a request becomes 10 RPC hops.
- "Microservices because resume" — the most common bad reason.
Big-ball-of-mud monolith#
- One repo, no module boundaries.
- Long release cycles; releases batch dozens of changes.
- Adding an engineer slows the team down rather than speeding it up.
- The fix isn't always microservices; it's modular monolith first.
Modular monolith — the sweet spot#
flowchart TB
subgraph App[Single deployable app]
subgraph OrderModule[Order module]
OAPI[Service]
ODB[(Schema: orders)]
end
subgraph CatalogModule[Catalog module]
CAPI[Service]
CDB[(Schema: catalog)]
end
subgraph UserModule[User module]
UAPI[Service]
UDB[(Schema: users)]
end
end
OAPI -. in-process API call .-> CAPI
OAPI -. in-process .-> UAPI
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class OAPI,CAPI,UAPI service;
class ODB,CDB,UDB datastore;
- Strict module boundaries (separate schemas, no shared models).
- In-process API call, not network.
- When you outgrow it, peel a module into its own service — the boundary already exists.
Migration paths#
- Monolith → Modular monolith: introduce module boundaries, restrict cross-module access to APIs only, separate DB schemas.
- Modular monolith → Microservices: peel one module out; introduce mesh, observability, service registry; repeat.
- Microservices → Modular monolith: re-merge low-traffic services into a single deployable to cut operational cost.
What you'll get asked#
- "When do you go to microservices?" When team coordination overhead exceeds operational overhead — usually past 50-100 engineers, or earlier if independent deploys matter.
- "What about the strangler fig?" Standard migration: route new functionality to a new service, gradually peel from the monolith.
- "How to share data?" Each service owns its DB. Cross-service queries via APIs or events. CDC for derived stores.
Related fundamentals#
- Cell-Based Architecture
- Container Orchestration
- Service Mesh
- Sidecar Pattern
- BFF Pattern
- Multi-Tenancy Patterns
FAQ#
When should I use microservices instead of a monolith?#
Use microservices when team count exceeds the coordination capacity of one codebase, parts of the system scale very differently, or you need independent deployments. Otherwise a modular monolith is faster and cheaper.
What is a modular monolith?#
A modular monolith is one deployable artifact internally split into clear modules with stable interfaces. You get most of the boundaries microservices provide without the network, deploy, and observability tax.
Are microservices always faster than monoliths?#
No. Microservices add network hops, serialisation, and distributed failure modes that often make request latency higher than a monolith. They scale teams and deployments, not raw per-request speed.
How do I migrate a monolith to microservices?#
Identify high-churn, high-isolation modules, expose them behind APIs inside the monolith, then extract them one at a time using the strangler fig pattern. Move data ownership last, not first.
What is the biggest hidden cost of microservices?#
Operational complexity. You need solid CI, distributed tracing, service discovery, and on-call coverage per service. Many teams underestimate this and end up with a distributed monolith that combines both downsides.