Distributed systems
Monolith vs microservices
Short answer
Start with a modular monolith unless independent deployment, scaling, or failure isolation is already a hard requirement. Adopt microservices around stable business boundaries when a team can operate service discovery, observability, retries, data ownership, and deployment complexity without turning every feature into a distributed transaction.
Written and reviewed by Sahil Srivastav
What each one actually is
A monolith deploys multiple capabilities as one process or release, often with one database. A modular monolith still enforces boundaries in code and ownership; it is not permission to share every table and global.
Microservices split capabilities into independently deployable processes with network calls and usually separate data ownership. The split buys autonomy and isolation while making latency, partial failure, and compatibility part of ordinary programming.
The useful comparison is reversible complexity. A well-modularised monolith can be split later; a premature service split creates contracts and operations that are difficult to retract.
Side by side
| Monolith | Microservices | |
|---|---|---|
| Deployment | One release coordinates all modules | Services release independently |
| Failure boundary | A process failure can affect many capabilities | Failures can be isolated, but calls can fail partially |
| Data transactions | Local multi-table transactions are straightforward | Cross-service invariants need sagas, outbox, or redesign |
| Latency | In-process calls are cheap | Network hops add latency and timeout paths |
| Scaling | Scale the whole application or use internal controls | Scale a hot service independently |
| Team ownership | Shared codebase and release coordination | Clear ownership, plus contract and platform work |
| Debugging | One trace and process are easier to inspect | Distributed tracing and correlation IDs are essential |
| Operational cost | Lower baseline | More deployables, alerts, credentials, and runbooks |
Choose Monolith when
- The product and boundaries are still changing rapidly
- One team can release and scale the application adequately
- Business operations need atomic updates across several modules
- The team does not yet have mature tracing, deployment, and incident practices
Choose Microservices when
- A capability has a demonstrably different scale or availability requirement
- Several teams need independent release ownership around stable boundaries
- A failure or compliance boundary requires process isolation
- The platform can provide standard discovery, telemetry, deployment, and secrets
The trade-off in detail
A service boundary does not remove coupling; it changes compile-time coupling into runtime coupling. A renamed field becomes a rollout protocol, and an unavailable dependency becomes a user-visible failure. Treat API compatibility and deadlines as part of the feature.
Separate databases improve ownership but make reporting and workflows harder. An outbox plus projections can preserve events, but it introduces lag, replay, and reconciliation work. Budget for those mechanisms before splitting.
A modular monolith is only a credible default if modules have boundaries: private data access, explicit interfaces, and tests that prevent accidental imports. Otherwise the later extraction will be a rewrite.
Things that are commonly said and are wrong
- “Microservices automatically scale better.” Only the hot service scales independently; coordination and shared dependencies may remain the bottleneck.
- “A monolith means one giant code file.” Deployment shape and code modularity are separate choices.
- “Separate databases guarantee independence.” Shared identity, queues, DNS, or a common platform can still couple outages.
FAQ
When is a monolith too large?
When its deployment, ownership, or scaling constraints are measurably blocking the team. Lines of code alone are not a threshold; a modular monolith can be large and healthy.
Can I split a monolith incrementally?
Yes. Establish a module boundary, move ownership behind an interface, add contract tests, then extract one capability with an anti-corruption layer. Do not begin by sharing tables across the new network boundary.
Do microservices require separate databases?
They should own their data, but the physical arrangement can vary during migration. Shared writes create hidden coupling; make one service authoritative and expose an API or event for consumers.