Modular Monoliths
Modular monoliths combine the modularity and clear boundaries of microservices with the operational simplicity of a monolithic deployment. Shopify, GitHub, and Basecamp run massive systems on this pattern — getting team autonomy and clear architectural boundaries without taking on distributed-system complexity.
What Makes a Monolith Modular
In a modular monolith, modules aren’t just folders or packages — they’re architectural boundaries. Each module represents a separate business capability with its own data model, business logic, and a clear interface to the rest of the system.
Modules talk through well-defined interfaces, not direct method calls or shared database access. When the orders module needs user info, it doesn’t query the user database — it calls the user module’s API. The interface might compile down to in-process method calls for performance, but the architectural boundary is explicit.
Enforcing Boundaries
The hardest problem with modular monoliths is enforcing boundaries without the network barrier microservices give you for free. Discipline alone doesn’t scale; tooling does.
DDD as the Foundation
The most successful modular monoliths align module boundaries with DDD bounded contexts. Each module represents a bounded context — an area with its own consistent business concepts, rules, and terminology.
For an e-commerce platform: customer management, order processing, inventory, payments, shipping. Each is a module with its own model of shared concepts. The customer-management module has detailed customer profiles. The shipping module has only delivery addresses and preferences.
Implementation Patterns
Hexagonal modules
Each module structures itself with hexagonal architecture (ports and adapters). Core business logic at the center, surrounded by ports that define interfaces for external communication, and adapters that implement those interfaces.
This pattern shines in modular monoliths because boundaries become explicit. Other modules interact only through defined ports, never with internal implementations.
Event-driven communication inside the monolith
Even in a single process, modules can communicate through events rather than direct calls. When an order completes, the Orders module publishes an OrderCompleted event. Inventory, Notifications, and Analytics modules consume it independently.
This produces the same loose-coupling benefits as microservices, while keeping the simplicity of in-process communication. Events can be processed synchronously for immediate consistency or asynchronously for performance.
Database patterns
Managing data is one of the trickier problems with modular monoliths. Useful patterns:
Evolution: Both Directions
One of the most powerful aspects of modular monoliths is evolutionary flexibility. They can grow from simple monoliths and, if needed, split into microservices — or consolidate back from microservices when operational complexity becomes excessive.
A well-designed modular monolith is an excellent launchpad for microservices. Teams experiment with service boundaries, refine interfaces, and build operational maturity — all while keeping deployment simple. When extraction makes sense, the architectural foundation is already there.
Why Smart Teams Choose This Pattern
Where They Struggle
Other limits:
- Limited team autonomy. Teams share infrastructure choices, branching strategy, deployment cadence. Microservices teams pick their own; modular-monolith teams compromise.
- Scaling ceilings. All modules share runtime, memory, deployment cycle. For truly massive systems, eventual extraction may be required.
- Refactoring across modules is harder than within services. When boundaries need to move, more coordination is required than in loosely coupled microservices.
Recap
- Modular monoliths combine module-level architectural discipline with single-deployment simplicity.
- The boundary is enforced by tooling and discipline — not network barriers.
- Each module is a bounded context with its own data, interface, and ownership.
- Communication via well-defined APIs (or in-process events), never direct DB access.
- Excellent middle ground for most teams; can be a long-term solution or a launchpad for microservices.
- The biggest risk is boundary erosion — invest in tooling that enforces it.