← All posts

Modular Monoliths: The Architecture That Dares to Stay Together

August 8, 2025

Modular monoliths offer the modularity and clear boundaries of microservices with the simplicity of monolithic deployment. Companies like Shopify, GitHub, and Basecamp have built massive systems this way, achieving team autonomy and clear boundaries without distributed systems complexity.

What Makes a Monolith Modular

The difference between a modular monolith and a traditional monolith isn’t in how they’re deployed — both are single units. The difference lies in their internal organization and the discipline with which boundaries are maintained.

Modules as First-Class Citizens

In a modular monolith, modules aren’t just folders or packages — they’re architectural boundaries. Each module represents a distinct business capability with its own data model, business logic, and clear interface to the rest of the system. A module might handle user management, order processing, or inventory tracking, but it does so as a self-contained unit.

Unlike regular monoliths, modular monoliths enforce explicit boundaries between business domains through code organization and tooling. Modules communicate only through defined APIs and own their data completely, enabling independent team development while maintaining single-deployment simplicity.

These modules communicate through well-defined interfaces, not direct method calls or shared database access. When the order module needs user information, it doesn’t directly query the user database — it calls the user module’s API. This interface might be implemented as in-process method calls for performance, but the architectural boundary remains clear.

Enforced Boundaries Through Discipline and Tooling

The critical challenge of modular monoliths is boundary enforcement. Without the network boundary that naturally separates microservices, it becomes tempting to take shortcuts — to directly access another module’s database or call internal methods. This is where discipline and tooling become essential.

Modern frameworks provide mechanisms to enforce these boundaries. Spring Modulith offers compile-time verification of module boundaries, ensuring that modules only communicate through designated interfaces.

Domain-Driven Design as the Foundation

The most successful modular monoliths align their module boundaries with Domain-Driven Design principles. Each module represents a bounded context — an area where certain business concepts, rules, and terminology apply consistently.

In an e-commerce system, you might have modules for Customer Management, Order Processing, Inventory Control, Payment Processing, and Shipping Coordination. Each module has its own model of core concepts like “Customer” or “Product,” optimized for its specific use case. The Customer Management module’s view of a customer includes detailed profile information and preferences, while the Shipping module’s view includes only address and delivery preferences.

Implementation Patterns and Strategies

Building effective modular monoliths requires intentional design patterns that maintain clear boundaries while enabling efficient communication between modules.

The Hexagonal Module Pattern

Each module can be structured using hexagonal architecture (ports and adapters), with clear separation between business logic and external concerns. The module’s core business logic sits at the center, surrounded by ports that define interfaces for external communication with other modules, databases, APIs, and external systems, and adapters that implement those interfaces.

This pattern works particularly well in modular monoliths because it makes the boundaries explicit. Other modules interact with the business logic only through the defined ports, never directly with internal implementations.

Event-Driven Communication Within the Monolith

Even within a single process, modules can communicate through events rather than direct calls. When an order is completed, the Order module publishes an “OrderCompleted” event that the Inventory, Notification, and Analytics modules can consume.

This event-driven approach provides the same loose coupling benefits as microservices while maintaining the simplicity of in-process communication. Events can be processed synchronously for immediate consistency or asynchronously for better performance, depending on business requirements.

Shared Kernel and Anti-Corruption Layers

Some concepts legitimately span multiple modules — user identity, common value objects, or shared business rules. The Shared Kernel pattern allows modules to depend on common abstractions while maintaining their independence for business logic.

When modules need to interact with external systems or legacy components that don’t follow clean boundaries, Anti-Corruption Layers provide translation between the module’s clean internal model and the messy external reality.

Technology and Tooling Support

The tooling ecosystem for modular monoliths has matured significantly, providing concrete support for building and maintaining modular systems.

Spring Modulith: Architecture Verification for Java

Spring Modulith brings first-class support for modular monoliths to the Spring ecosystem. It provides tools to verify that module boundaries are respected at compile time, generate documentation of module relationships, and test modules in isolation.

Spring Modulith allows developers to define modules as Java packages with explicit public APIs. Any attempt to access non-public components from another module results in compilation errors, enforcing architectural boundaries automatically.

.NET and Modular Design

The .NET ecosystem has long supported modular design through assemblies and namespaces. Modern .NET provides additional tools like internal accessibility modifiers and assembly-level access control that help maintain module boundaries.

Projects like Wolverine and MediatR provide patterns for event-driven communication within .NET monoliths, enabling loose coupling between modules while maintaining high performance.

Database Module Patterns

One of the most challenging aspects of modular monoliths is data management. Several patterns help maintain data boundaries while leveraging shared infrastructure:

Schema per Module: Each module owns its database schema, with foreign key relationships only within module boundaries. Cross-module data access happens through module APIs, not direct database queries.

Database Views for Integration: Read-only views can provide controlled access to data across modules for reporting and analytics, while maintaining write boundaries through module APIs.

Event Sourcing for Module Communication: Events can serve as both integration points and audit trails, providing a clean way to share state changes between modules while maintaining autonomy.

Evolution Strategies: Growing and Shrinking Gracefully

One of the most powerful aspects of modular monoliths is their evolutionary flexibility. They can grow from simple monoliths and, when necessary, split into microservices — or consolidate back from microservices when operational complexity becomes overwhelming.

From Monolith to Modular

The journey from a traditional monolith to a modular monolith typically begins with identifying domain boundaries within the existing codebase. This process often reveals that the system already has natural seams — areas where responsibilities are clearly separated and coupling is minimal.

The Strangler Fig pattern works well for this transformation. Instead of attempting a big-bang restructuring, teams gradually extract functionality into well-defined modules, establishing clear interfaces and removing direct dependencies over time.

Module Extraction to Microservices

When organizational growth or technical requirements justify the operational complexity of microservices, well-designed modular monoliths make the transition smoother. Each module can potentially become an independent service, with established boundaries and communication patterns.

The key insight is that modular monoliths serve as excellent staging grounds for microservices. Teams can experiment with service boundaries, refine interfaces, and build operational capabilities while maintaining the simplicity of monolithic deployment. When extraction makes sense, the architectural groundwork is already in place.

Microservices Back to Modular Monoliths

As we’ve seen with companies like Prime Video and Segment, sometimes the microservices journey leads back to consolidated architectures. Modular monoliths provide an excellent target for this consolidation — teams can maintain the clean boundaries and independent development practices they’ve built while reducing operational overhead.

The Benefits: Why Smart Teams Choose Modular Monoliths

The advantages of modular monoliths become apparent when compared to both traditional monoliths and microservices architectures.

Operational Simplicity with Architectural Sophistication

Single deployment unit means simplified CI/CD pipelines, easier rollbacks, and reduced coordination overhead. There’s one build to monitor, one deployment to coordinate, and one runtime environment to manage. Yet the internal architecture maintains the clean boundaries and separation of concerns that enable team productivity.

Consistent development environment: Developers can run the entire system locally, making debugging and testing more straightforward. There’s no need to mock dozens of external services or manage complex local orchestration.

Performance Without Compromise

In-process communication eliminates network latency and serialization overhead. What would require multiple HTTP calls in a microservices architecture can be efficient method calls in a modular monolith, while still maintaining architectural boundaries.

Transactional consistency across modules remains straightforward when needed. Business operations that span multiple domains can maintain ACID properties without the complexity of distributed transactions.

Team Productivity and Autonomy

Clear ownership boundaries enable teams to work independently on their modules while contributing to a shared codebase. Teams can make local decisions about implementation details while adhering to agreed-upon interfaces.

Reduced cognitive load: Developers need to understand module interfaces, not distributed system complexities. The mental overhead of network partitions, eventual consistency, and service discovery is eliminated.

The Challenges: Where Modular Monoliths Struggle

No architectural pattern is perfect, and modular monoliths face their own set of challenges that teams must navigate carefully.

Boundary Discipline

The biggest challenge is maintaining architectural boundaries without enforcement mechanisms. Unlike microservices, where the network boundary is physically enforced, modular monoliths rely on team discipline and tooling to maintain separation.

Technical debt accumulation can erode boundaries over time. Without constant vigilance, shortcuts and expedient solutions can couple modules in ways that undermine the architecture’s benefits.

Limited team autonomy: Unlike microservices where teams can choose their technology stack, database, git branching strategy and deployment strategy independently, modular monoliths constrain teams to shared infrastructure decisions and coordinated branching strategies.

Scaling Limitations

While modular monoliths can scale significantly, they eventually hit resource and team limits. All modules share the same runtime environment, memory space, and deployment cycle. For truly massive systems or organizations, these constraints may become limiting factors.

Deployment coordination still requires some level of coordination across teams, especially for database schema changes or interface modifications that affect multiple modules.

Evolution Complexity

Large-scale refactoring across module boundaries can be challenging. When business requirements change in ways that don’t align with existing module boundaries, restructuring requires more coordination than in loosely coupled microservices.

Conclusion

Modular monoliths provide modularity benefits with deployment simplicity, making them suitable for teams that need clear boundaries without microservices complexity. They work well when organizational scale doesn’t justify distributed systems overhead, but require discipline to maintain boundaries and may limit team technology choices. The architecture serves as either a long-term solution or a stepping stone to microservices when scaling demands change.