Problem
Classic monoliths decay into tangled code where everything calls everything, while microservices add heavy distributed-systems costs a team may not need. Teams want clear boundaries without network overhead.
Also known as: modulith, well-structured-monolith
Build a single deployable application composed of strongly isolated modules with explicit interfaces.
Classic monoliths decay into tangled code where everything calls everything, while microservices add heavy distributed-systems costs a team may not need. Teams want clear boundaries without network overhead.
A modular monolith keeps one deployable but treats modules like internal services: each owns its logic and data, and talks to others only through a published contract. It captures many benefits of microservices, such as clear ownership and low coupling, without network calls, distributed transactions, or per-service pipelines.
Modules map to business capabilities such as Catalog, Ordering, and Billing. Each has a public facade and private internals.
app/
ordering/ api/ (public) internal/ (private) schema: ordering
billing/ api/ (public) internal/ (private) schema: billing
catalog/ api/ (public) internal/ (private) schema: catalog
Rule: ordering may import billing.api, never billing.internal
Cross-module reactions use in-process events or the public API. Architecture tests, for example with ArchUnit or dependency-cruiser, fail the build on violations. Because calls are in-process, a failed call is an exception, not a timeout, and a single database transaction can still cover a workflow if needed, though keeping modules decoupled is better.
Low — single deployment
Low — one pipeline and runtime
Medium — boundary design and enforcement
Modules reaching into each other's tables, creating hidden coupling
Shared utility module that grows into a dumping ground
Boundaries never enforced, reverting to a big ball of mud