Modular Monolith
A modular monolith is one deployable with strictly enforced module boundaries and no cross-module data reach-ins, gaining microservice decoupling without the distributed tax.
Definition
A modular monolith runs as a single process and database while organizing code into modules with enforced boundaries: each module owns its data, exposes an API, and is consumed through that API only. Boundary enforcement can be architectural review, linters, or build-level module separation. The payoff is independent team evolution and a cheap future extraction path.
Context and scope
Chosen by teams wanting domain separation without operational distribution: Shopify, Basecamp-style Rails apps, and many internal platforms. The recommended first shape before microservices.
Common misconceptions
- A monolith with folders is already modular — modularity requires enforced boundaries; folders without rules decay into a big ball of mud
- Modules must share one database schema — each module owns its tables; shared schema recreates the coupling modules exist to prevent
- Microservices later means designing for them now — design clean module boundaries; distribution is an extraction decision made from evidence
Anti-patterns
- Modules quietly reading each other tables and coupling data models that were meant to evolve separately
- Skipping boundary enforcement tooling because the team is disciplined — discipline erodes exactly when hiring scales
- Building a distributed system of two services because microservices, and paying network failure modes for zero scale benefit
Related concepts
Authoritative source
What it is
The useful part of microservices is bounded contexts with explicit contracts; the expensive part is distribution. Modular monoliths keep the first and decline the second.
How it works
Modules encapsulate domain and data. Cross-module access happens through public interfaces — in-process calls that still cross an architectural boundary, enforced by dependency checks in CI. Module-owned database tables get schema access rules. Transactional consistency stays local, which is the single biggest simplification versus distribution.
When to choose
Small-to-medium teams, domains that partition cleanly, and scaling needs satisfied by vertical growth. Extraction paths remain: a module with a hot API can become a service later, with its module API as the seam.
Pitfalls
Boundary erosion is gradual: one convenience import, then a shared table, then the module line is fiction. Keep enforcement automated. In-process calls tempt synchronous chains that must eventually become events for time-decoupling — design the async seams inside modules even in-process.