New54 new lessons added since Sep 10!
Explore What's New →
Software Development Atlas
Engineering JudgmentDecision Guides

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.

EvergreenVerified Sep 9, 2026Review target: 365 days

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, and microservices

Monolith

One process
Shared internal modules
Often one transactional boundary

Modular monolith

One deployment
Explicit module boundaries
Local calls + shared transaction options

Microservices

Independent deployments
Network APIs / events
Separate failure + consistency boundaries
The key difference is where module, deployment, process, data, and network boundaries are drawn.

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.

Monolith vs modular monolith vs microservices decision matrix
CriterionMonolithModular monolithMicroservices
Deployment lifecycleOne application deploymentOne application deployment; modules still ship togetherSelected service boundaries can deploy independently
Domain-boundary enforcementDepends on internal module/dependency disciplineExplicit module interfaces and dependency rules inside one deploymentInternal boundaries plus network/API and deployment contracts
Cross-boundary transaction modelCan remain inside one database/transaction boundary when the design permitsCan remain local across modules that share the transactional storeCross-service workflows usually need explicit distributed consistency/recovery design
Team release autonomyTeams coordinate the shared application releaseCode ownership can be separate, but deployment is still coordinatedA team can release its service independently when ownership and compatibility boundaries are real
Failure boundariesOne process/deployment failure can affect the application unitSame deployment failure boundary; modules can still isolate logic/data errors internallyService failures can be isolated from other processes, while callers must handle partial/ambiguous remote failure
Scaling unitScale the application deploymentScale the application deploymentScale selected services separately when the platform and dependencies support it
Operational workOne deployable/runtime surface, plus application dependenciesOne deployable plus explicit module-boundary governanceOperational 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.

Failure blast radius is a design outcome

Shared-process failure

Reports leaks memory
Process reaches OOM
Checkout + auth also unavailable

Isolated service + guardrail

Reports service fails
Circuit breaker opens
Core checkout continues
A smaller process boundary can isolate one failure, but network dependencies can introduce new cascading failures.

Microservices change the consistency and recovery model

Local ACID transaction vs distributed recovery

Local transaction

Write A
Write B
Commit or rollback together

Distributed workflow

Service A commits
Service B times out
Reconcile / compensate / retry
Cross-service work cannot rely on one process call stack or one database rollback boundary.

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

The distributed monolith anti-pattern
  1. Service A
    Must call B synchronously
  2. Service B
    Must call C synchronously
  3. Service C
    Shared database assumptions
  4. Lockstep release
    Operational cost without autonomy
Separate deployables do not provide autonomy when changes, data, and synchronous calls remain tightly coupled.

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:
    1. For small teams without proven domain stability, keep registration inside a Modular Monolith to complete the workflow within a single ACID transaction (BEGIN ... COMMIT).
    2. If separate microservices are mandatory, use a Transactional Outbox and reliable asynchronous event publishing via a broker (e.g. Kafka or RabbitMQ) so profile-service reliably receives the event and completes registration.

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:

  1. Can better internal module boundaries solve the current problem? Prefer that when deployment independence is not required.
  2. Which exact boundary needs independent lifecycle? Name the release, ownership, scaling, availability, or isolation constraint.
  3. What changes when the call/data crosses a network? Identify timeout, retry, transaction, consistency, and compatibility consequences.
  4. Who operates the new service during an incident? Include dashboards, alerts, dependencies, runbooks, rollback, and ownership.
  5. 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:

  1. 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.
  2. 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).
  3. 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.
  4. 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?

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

On this page