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 22, 2026
TL;DR
Engineering teams rarely suffer outages because their codebase was labeled a "monolith." They suffer because they sliced an application into 20 microservices to mimic Big Tech resume-driven architectures, only to discover that routine pull requests now require synchronized deployments across five repositories, debugging demands distributed tracing specialists, and a single flaky internal network hop triggers cascading timeouts. Architectural style is not a ladder of engineering prestige—it is a pragmatic calculation trading in-process ACID transactions and operational simplicity for team release autonomy and selective workload scaling.
💡 Rule of thumb: Start with the least distributed deployment model that satisfies your hard constraints. Enforce strict domain boundaries in code before introducing network hops. Only extract a microservice when independent deployment creates measurable value—such as removing concrete release coordination bottlenecks, isolating volatile scaling workloads, or establishing explicit failure containment.
- Deployment shape is not code quality: A monolith can feature pristine modular boundaries enforced by compiler rules, while microservices can easily degenerate into a tangled "distributed monolith" communicating over fragile synchronous HTTP calls.
- In-process execution preserves local ACID transactions: Monoliths and modular monoliths leverage shared memory and single-database transactions, eliminating the massive distributed coordination tax of two-phase commits, Sagas, or compensating workflows.
- Independent deployment demands platform maturity: Microservices grant autonomous release schedules to separate product teams, but require automated CI/CD pipelines, distributed tracing, and rigorous handling of partial failure and contract compatibility.
- Operational complexity depends on deployment count: Infrastructure overhead, telemetry pipelines, and on-call surface areas scale directly with the number of autonomous deployables rather than total lines of code.
- Fatal pitfall: Premature microservice extraction without stable domain boundaries. Splitting a system before business aggregates and data boundaries stabilize forces cross-service database joins, distributed transactions, and lockstep releases—imposing the latency, cost, and failure modes of distributed systems with none of the autonomy benefits.
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?
Monolith
Modular monolith
Microservices
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.
Shared-process failure
Isolated service + guardrail
Microservices change the consistency and recovery model
Local transaction
Distributed workflow
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
- Service AMust call B synchronously
- Service BMust call C synchronously
- Service CShared database assumptions
- Lockstep releaseOperational cost without autonomy
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
A 15-engineer startup split their core application into 12 microservices before finding product-market fit. During a routine user registration flow, the auth-service succeeded in creating a credential, but the downstream profile-service timed out due to network jitter:
- Impact: The user was left in a zombie state—unable to log in (profile missing) and unable to re-register because the email was already taken in
auth-service. The engineering team spent 30% of their sprint time writing manual reconciliation scripts to clean data discrepancies across 12 separate databases. - Root cause: Introducing network and database boundaries without preparing for partial failure, distributed transactions, or eventual consistency.
- Correct pattern:
- For small teams without proven domain stability, keep registration inside a Modular Monolith to complete the workflow within a single ACID transaction (
BEGIN ... COMMIT). - If separate microservices are mandatory, use a Transactional Outbox and reliable asynchronous event publishing via a broker (e.g. Kafka or RabbitMQ) so
profile-servicereliably receives the event and completes registration.
- For small teams without proven domain stability, keep registration inside a Modular Monolith to complete the workflow within a single ACID transaction (
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.
Check your mental model
Scenario: A team of 6 engineers manages an e-commerce platform deployed as a single monolith. Builds take 12 minutes, and deployments happen once every two days. An engineer proposes: "Let's break the monolith into 8 microservices immediately so our builds run faster and each developer can deploy independently."
How should an architecture reviewer evaluate this proposal?
Show the reasoning
Why this proposal is premature and likely counterproductive:
- Team size vs. operational overhead: A 6-engineer team usually lacks the dedicated platform capacity to operate 8 distinct CI/CD pipelines, service discovery, distributed tracing, independent databases, and separate on-call rotations.
- Cheaper solutions to the actual problem: A 12-minute build and deployment every two days is not severe deployment contention. It can often be improved directly within the monolith (e.g., test parallelization, caching, or modular packaging).
- Distribution tax: Moving to 8 microservices replaces local in-process calls with network requests, introducing latency, partial failure modes, and distributed data consistency challenges that far outweigh the build-time savings.
- Better path: Implement a Modular Monolith first: enforce clear boundaries between modules (e.g., catalog, cart, checkout) using package boundaries and lint rules while keeping a single deployable. Extract a specific service only when a concrete boundary demonstrates independent scaling or release requirements.
Decision review checklist
Use this checklist during architecture design reviews before committing to deployment boundaries:
- Boundary stability: Are the domain boundaries stable enough to defend with interfaces before extracting them across a network?
- Measured delivery friction: Is coordinated deployment actually delaying releases or increasing risk for separate owning teams?
- Transaction boundaries: Which transactions would cross a service boundary, and what is the recovery mechanism if a downstream step fails?
- Partial failure handling: What happens when an upstream service succeeds and a downstream service times out with an ambiguous result?
- Cross-service telemetry: How will correlation IDs, distributed traces, and logs trace a single user action across services?
- Incident ownership: Does each service have a durable owning team with direct on-call and runbook responsibility?
- Empirical scaling requirements: Are independent scaling/availability requirements observed in production metrics or merely speculative?
- In-code enforcement first: Have module boundaries and dependency rules been tested and enforced inside a single deployment before splitting?
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.