Modular monolith
A modular monolith is one application, deployed as one unit, whose code is divided into modules along business lines (orders, billing, catalog). Each module hides its internals and exposes a small, explicit interface. Other modules may use only that interface.
It aims for most of the organizational benefits of microservices without the cost of running a distributed system.
Diagram
Section titled “Diagram”flowchart TB
api[API and UI] --> orders & billing & catalog
subgraph app [One deployable application]
orders[Orders module]
billing[Billing module]
catalog[Catalog module]
orders -- public interface --> billing
orders -- public interface --> catalog
end
orders --> s1[(orders schema)]
billing --> s2[(billing schema)]
catalog --> s3[(catalog schema)]
How it works
Section titled “How it works”- Modules follow business capabilities. Each module owns one area of the domain, end to end.
- Explicit public interfaces. Modules call each other only through published APIs, never through internal classes.
- Data ownership per module. Each module has its own tables or schema. Other modules don’t query them directly.
- Boundaries enforced by tooling. Build rules, architecture tests or language-level module systems make it fail the build when a module reaches into another’s internals.
- Events inside the process. Modules can communicate through in-process events to keep coupling low.
- Simple operations: one deployment, one pipeline, one set of dashboards.
- Fast, reliable communication: in-process calls don’t fail like network calls do.
- Ordinary transactions: consistency across modules can use one database transaction when truly needed.
- Cheap to restructure: moving a boundary is a refactor, not a migration.
- A path to microservices: a well-isolated module can later be extracted into a service.
- Scales as one unit: you can’t scale a single busy module on its own.
- Shared release train: all teams deploy together, so one team’s bug can block another’s release.
- Boundaries erode without enforcement: shortcuts between modules are always one import away.
- One technology stack: all modules share a language and runtime.
When to use it
Section titled “When to use it”- Most new products, especially while the domain is still being discovered.
- Small to medium organizations with one to a few teams.
- Systems that need strong consistency across business areas.
When to avoid it
Section titled “When to avoid it”- Many teams are blocked by a shared release process.
- Parts of the system have very different scaling, availability or technology needs.
See Microservices vs modular monolith for a full comparison.
Common pitfalls
Section titled “Common pitfalls”- Modules by layer instead of by business. A “services module” and a “repositories module” isn’t a modular monolith; it’s a layered architecture.
- Shared tables. If two modules write to the same table, they aren’t separate modules.
- No automated boundary checks. Rules that only live in a wiki will be broken within months.
Related patterns
Section titled “Related patterns”- Microservices
- Layered architecture
- Hexagonal architecture
- Strangler fig migration
Glossary: bounded context, coupling
Further reading
Section titled “Further reading”- Simon Brown, “Modular monoliths” (talk, available online)
- Sam Newman, Monolith to Microservices
Written and edited by Abhinav Taduka, enterprise software architect.