vishal patel
UnderstoodIntermediateUpdated 2026-09-23

Modular Monolith

One deployable unit, internally split into strongly bounded modules with explicit public APIs — microservice-style boundaries without the distributed-systems tax.

monolithbounded-contextdddevolution

The problem

Teams jump to microservices to get boundaries, but they also inherit network failures, distributed transactions, and N pipelines to maintain. Most of the value came from the boundaries, not the network hops.

How it works

diagram

Rules that make it modular rather than a "big ball of mud":

  1. Each module exposes a small public API. Everything else is internal, enforced by lint rules, package boundaries, or tools like dependency-cruiser or ArchUnit.
  2. Each module owns its data (its own schema or collections). No cross-module joins.
  3. Modules talk synchronously through the public API, or asynchronously through in-process events.
  4. Any module can later be extracted into a service without a rewrite.
Use it when
  • New products where domain boundaries are still moving
  • One team or a few teams
  • You want fast refactoring and one transaction boundary
Avoid it when
  • Modules need very different scaling or runtimes (GPU vs CRUD)
  • Many teams need fully independent release cadences

Trade-offs

  • ✅ One deploy, one debugger, local transactions, simple ops.
  • ✅ Keeps the option to extract services later, with boundaries already proven.
  • ❌ Boundaries erode unless they're enforced by tooling.
  • ❌ It all scales together, and one bad module can take down the process.
Where I've used it

For the 15-module field-sales CRM, the design direction is a modular monolith: one Next.js/Node deployable, with each module owning its collections and exposing a service interface. Extraction stays possible, but nobody pays microservice costs on day one.

Sources & further learning

Videos, courses, docs and books I recommend for this topic.

Related topics