Architecture comparison
Microservices vs monolith
One of the most over-debated decisions in software architecture. Both can work; both can fail. The right answer depends on team size, organisational coupling, and how much operational complexity you can absorb.
TL;DR
- Monolith — one codebase, one deployable, one database. Simple operations, fast iteration in early-stage product.
- Microservices — many independently deployable services, each owning data. Team autonomy and targeted scaling, at the cost of distributed-systems complexity.
- Most teams should start monolithic and split out services only when team or scale pain is concrete — not anticipated.
Side-by-side comparison
| Aspect | Monolith | Microservices |
|---|---|---|
| Deploy unit | One application | Many services, independent deploys |
| Codebase | One repository (or monorepo) | One repo per service (or shared monorepo) |
| Database | Usually one shared DB | One DB per service (database-per-service pattern) |
| Communication | In-process function calls | HTTP/gRPC + message queues over the network |
| Failure mode | All-or-nothing | Partial — one service down, others keep running (if designed for it) |
| Scaling | Whole app together | Per-service, independently |
| Tech stack | One language/framework | Each service can choose its own (rarely good idea) |
| Team coupling | High — shared code, shared release | Low — teams ship independently |
| Operational cost | Low — one thing to monitor | High — distributed tracing, service mesh, etc. |
| Sweet spot | 1–50 engineers, single product | 50+ engineers, many product areas |
Architecture sketch
Monolith
┌─────────────────────────┐
│ Load balancer │
└───────────┬─────────────┘
▼
┌─────────────────────────┐
│ App (single process) │
│ ┌───────┬────────────┐ │
│ │ Auth │ Orders │ │
│ ├───────┼────────────┤ │
│ │ Users │ Payments │ │
│ └───────┴────────────┘ │
└───────────┬─────────────┘
▼
┌────────┐
│ DB │
└────────┘ Microservices
┌─ Auth svc ──→ Auth DB
LB ──→ API GW ──┼─ Users svc ─→ Users DB
├─ Orders svc → Orders DB
└─ Payments ──→ Payments DB
┌────────────┐
│ Message │
│ broker │ ← events between services
└────────────┘ When to stay monolithic
- Small team. Under ~50 engineers, monolith communication overhead is far cheaper than service overhead.
- Single product / domain. Clear bounded contexts haven't emerged yet — splitting too early creates wrong boundaries.
- Operational simplicity matters. One log stream, one metrics dashboard, one deploy pipeline.
- Tight coupling between features. When most features touch most data, a network call between them is just slow.
- Need fast iteration. Cross-service refactors are dramatically slower than monolith refactors.
When microservices pay off
- Many teams, parallel delivery. Independent deploys remove cross-team coordination overhead.
- Heterogeneous scaling needs. Image processing and the customer dashboard have very different traffic patterns.
- Clear domain boundaries. Billing, search, and inventory really are separable concerns.
- Compliance / isolation. Payment-handling service needs PCI scope; isolation reduces audit surface.
- Polyglot needs. ML pipeline in Python, API in Go, real-time in Elixir — but be honest about the maintenance tax.
English phrases engineers use
Monolith conversations
- "We have a well-modularised monolith — bounded contexts are clear inside."
- "Every PR is a shared release — coordination is killing us."
- "The build is now 15 minutes — we need to split or shard."
- "This is a distributed monolith — multiple services but every change touches all of them."
- "Strangler fig: route this endpoint to the new service, keep the rest in the monolith."
Microservices conversations
- "What's the service boundary here — by domain or by access pattern?"
- "This is creating a chatty interface — too many round trips per request."
- "Database per service — Orders doesn't read from the Users DB directly."
- "The blast radius of this change is limited to one service."
- "Saga pattern for the multi-service transaction — no distributed locks."
Quick decision tree
- Greenfield, < 20 engineers → Monolith
- 20–50 engineers, single product → Modular monolith
- 50+ engineers, multiple product areas → Microservices (carefully)
- Compliance isolation required (PCI, HIPAA) → Split out the scoped service
- One feature has very different scaling needs → Extract that feature only
- "We want microservices because they're modern" → Stay monolithic
- Existing monolith is painful → Strangler fig migration
- Migrating, unsure about boundary → Make the wrong split reversible
Frequently asked questions
What is the real difference between a monolith and microservices?
A monolith is one application that is built, deployed, and scaled as a single unit. Microservices break that application into many small services, each owning its own data, deployable on its own schedule, communicating over the network. The trade-off is operational simplicity (monolith) versus team independence and targeted scaling (microservices).
Are microservices always better?
No — they are almost always worse for small teams. Microservices add network calls, distributed tracing, schema versioning, service discovery, and deployment complexity. For a team under ~50 engineers with a single product, a well-structured "modular monolith" is usually faster to ship and easier to operate.
When does the monolith stop working?
Common pain points: deployments coordinate across many teams, the codebase becomes a merge-conflict minefield, scaling one feature requires scaling everything, technology choices are locked in, and a single bad commit can take down the whole product. When two or more of these hurt regularly, splitting parts out begins to pay off.
What is a modular monolith?
A single deployable application internally structured as well-bounded modules — each module has clear interfaces, owns its data, and could in principle be extracted. This gives you most of the team-organisation benefits of microservices without paying the operational tax. Most "microservices migration" stories end up here in practice.
How do microservices communicate?
Synchronously via HTTP/REST or gRPC for request-response patterns, asynchronously via message queues (Kafka, RabbitMQ, SQS) or pub/sub for event-driven flows. Most production systems use both: HTTP for user-facing queries, async messaging for fan-out and decoupling.
What is the biggest mistake teams make with microservices?
Splitting services along technical layers (one for the database, one for the API, one for auth) instead of along business capabilities ("billing", "orders", "inventory"). Bad boundaries mean every feature change touches every service, and you end up with a distributed monolith — the worst of both worlds.
Can I migrate from monolith to microservices gradually?
Yes — the strangler fig pattern is the standard approach: keep the monolith running, build new functionality as services, and slowly route traffic away from the old monolith to the new services. Don't attempt a "big-bang rewrite" — that almost always fails or takes 3× longer than planned.