Monolith vs Modular Monolith vs Microservices
Choose deployment and domain boundaries by team ownership, transaction needs, failure isolation, scaling pressure, and operational capacity rather than architecture prestige.
Personal learning atlas by Tran Trong Thuc · About this Atlas · Atlas last updated Sep 9, 2026
TL;DR
A monolith, modular monolith, and microservices architecture are not three maturity levels. They place code, data, deployment, and ownership boundaries differently.
Start with the least distributed deployment model that meets the real constraints. Improve internal modularity before assuming a network boundary is necessary. Extract a service when independent deployment creates measurable value—for example by removing real release coordination, isolating a workload that must scale separately, creating a deliberately operated failure boundary, or giving one team ownership it cannot achieve inside a shared deployment.
The question is not “Can this be a service?” Ask: which constraint becomes easier after this boundary becomes independently deployed, and how will we measure that improvement?
Decision frame
Write down the forces before choosing an architecture label:
- How many teams genuinely need independent release schedules?
- Which business capabilities have stable ownership and different change rates?
- Which operations benefit from one local ACID transaction?
- Which workloads need materially different scaling, availability, isolation, or runtime characteristics?
- Can the organization operate multiple independently failing deployments, data stores, dashboards, alerts, compatibility contracts, and on-call surfaces?
- Is current pain caused by coordinated deployment, or poor module boundaries inside the codebase?
- What measurable release-time, runtime, or incident-response outcome would improve if a boundary became a service?
The options
Monolith
A monolith is deployed as one application unit. It can be well modularized or tightly coupled; “monolith” describes deployment shape, not code quality.
Local calls can stay local, transactions can often remain inside one database boundary, and debugging does not inherently cross service networks. The trade-off is that modules share the deployment lifecycle.
Modular monolith
A modular monolith keeps one deployment while enforcing explicit domain boundaries inside it. Modules expose deliberate interfaces, hide implementation details, and can have ownership/dependency rules even though they ship together.
This is useful when teams need stronger code/domain boundaries but coordinated deployment remains acceptable. It also tests whether a proposed boundary is real: if the boundary cannot stay clean inside one process, putting it behind a network API usually preserves the conceptual coupling while adding distributed-system failure modes.
A modular monolith can be a long-lived final architecture.
Microservices
A microservices architecture moves selected domain boundaries into independently deployable services, usually with explicit network APIs/events and separately owned state boundaries.
That deployment independence can matter when teams need separate release schedules, selected workloads need isolated scaling/availability policy, or one capability needs a different runtime lifecycle. The cost is structural: local calls become remote, compatibility spans deployed versions, data consistency can cross transaction boundaries, and observability must correlate work across services.
| Criterion | Monolith | Modular monolith | Microservices |
|---|---|---|---|
| Deployment lifecycle | One application deployment | One application deployment; modules still ship together | Selected service boundaries can deploy independently |
| Domain-boundary enforcement | Depends on internal module/dependency discipline | Explicit module interfaces and dependency rules inside one deployment | Internal boundaries plus network/API and deployment contracts |
| Cross-boundary transaction model | Can remain inside one database/transaction boundary when the design permits | Can remain local across modules that share the transactional store | Cross-service workflows usually need explicit distributed consistency/recovery design |
| Team release autonomy | Teams coordinate the shared application release | Code ownership can be separate, but deployment is still coordinated | A team can release its service independently when ownership and compatibility boundaries are real |
| Failure boundaries | One process/deployment failure can affect the application unit | Same deployment failure boundary; modules can still isolate logic/data errors internally | Service failures can be isolated from other processes, while callers must handle partial/ambiguous remote failure |
| Scaling unit | Scale the application deployment | Scale the application deployment | Scale selected services separately when the platform and dependencies support it |
| Operational work | One deployable/runtime surface, plus application dependencies | One deployable plus explicit module-boundary governance | Operational work grows with deployable count, networks, data boundaries, compatibility, telemetry, and ownership surfaces |
Operational complexity depends on deployment count, infrastructure automation, ownership, data boundaries, reliability requirements, and the maturity of the surrounding platform. The matrix describes mechanisms, not universal low/medium/high scores.
When each model fits
Prefer a monolith when
- one team or a few closely coordinated teams own the product;
- the domain is changing quickly and stable service boundaries are not yet evident;
- coordinated deployment is not causing measurable delivery pain;
- local transaction/debugging simplicity matters more than independent service lifecycle.
A monolith is not permission to create a ball of mud. Explicit modules, tests, dependency rules, and ownership boundaries still matter.
Prefer a modular monolith when
- domain boundaries and code ownership need stronger enforcement;
- teams can coordinate one deployment;
- you want to reduce coupling before deciding whether independent deployment is necessary;
- important workflows still benefit from local transaction boundaries.
Prefer microservices when
- independent deployment solves a concrete, measured coordination problem;
- service boundaries align with durable business capabilities and clear owning teams;
- selected workloads require materially different scaling, availability, isolation, or release cadence;
- the organization can operate routing, telemetry, CI/CD, secrets, version compatibility, incident response, and asynchronous consistency across the intended number of services.
Examples of measurable value include shorter release lead time, reduced coordination with unrelated teams, independently enforced capacity/availability objectives, or a smaller runtime blast radius.
Microservices change the consistency and recovery model
Inside one process, a normal function call completes under one runtime failure boundary. Across a service boundary, the caller can time out without knowing the remote outcome.
Distributed boundaries introduce design work around timeouts, retry budgets, idempotency, partial failure, delivery semantics, tracing/correlation, eventual consistency, compensation, and compatibility between independently deployed versions.
Common traps
Treating microservices as an organizational shortcut
A network boundary does not create a coherent business boundary or clear ownership. If the model is tightly coupled, service APIs can preserve that coupling while making changes and failures harder to coordinate.
Mistaking repository size for deployment pressure
A large codebase may need module boundaries, build tooling, ownership, and dependency rules without requiring many deployables.
Using independent databases as an ideology
Service-owned data can improve autonomy when a service truly owns its business capability. But cross-service workflows must then model consistency and recovery explicitly.
Extracting before observing the pressure
Early service boundaries often encode assumptions rather than durable capabilities. A modular codebase lets you gather evidence about ownership, release cadence, scaling, and failure isolation before committing to a network boundary.
Practical heuristic
Ask these questions in order:
- Can better internal module boundaries solve the current problem? Prefer that when deployment independence is not required.
- Which exact boundary needs independent lifecycle? Name the release, ownership, scaling, availability, or isolation constraint.
- What changes when the call/data crosses a network? Identify timeout, retry, transaction, consistency, and compatibility consequences.
- Who operates the new service during an incident? Include dashboards, alerts, dependencies, runbooks, rollback, and ownership.
- How will we know the service boundary paid off? Define a measurable outcome before extracting it.
The extraction threshold is reached when independent deployment creates measurable value that a local module boundary cannot provide at lower cost.
Questions to ask before choosing
- Are the domain boundaries stable enough to defend with interfaces?
- Is coordinated deployment actually delaying releases or increasing risk?
- Which transactions would cross a service boundary?
- What happens when one side succeeds and the other side times out?
- How will traces/logs correlate one user action across services?
- Does each service have a durable owning team and incident responsibility?
- Are independent scaling/availability requirements observed or speculative?
- Could architecture tests enforce the desired boundary inside one deployment first?
Related concepts
This decision is primarily about Modularity, Domain Boundaries, Coupling and Cohesion, and the point at which Partial Failure becomes worth accepting. The Backend Systems path provides the reliability/data concepts that become essential once boundaries are distributed.
Sources
CSR vs SSR vs SSG
Choose a web rendering model by freshness, personalization, interactivity, cacheability, server work, and operational constraints instead of framework fashion.
Containers vs Serverless
Choose a cloud compute operating model by workload shape, startup sensitivity, runtime control, scaling behavior, portability, operational ownership, and cost predictability.