Modular Monolith
What a modular monolith is, how its modules keep their boundaries, and when it beats both a classic monolith and microservices. For architects structuring a product that several people or teams will grow for years.
- Researched
- 15 Oct 2026
- Reading time
- 6 min
- Sources
- 17
- Review due
- 15 Oct 2027
- One deployable, many modules: each module is a business capability with a small public contract and private internals.
- Boundaries need enforcement: project references, visibility and architecture tests, or modules decay into a big ball of mud.
- Each module owns its data: its own schema, no shared tables, communication through contracts or events.
- Monolith first: you learn the right boundaries in-process, where moving them is cheap, and extract services only when needed.
- Inside a module, use whatever fits: Clean, Hexagonal, Vertical Slice or plain CRUD.
On this page 9 sections
The idea in one minute
A modular monolith is one deployment unit whose code is split into modules with explicit public contracts and enforced boundaries. Kamil Grzybek puts the first half bluntly: a monolith is simply a system with exactly one deployment unit. The other half is the hard part: each module must be independent, deliver a complete business capability, and hide its internals behind a stable contract.[5]
It sits between two familiar extremes. A classic monolith has one deployable and no internal boundaries, so everything can reach everything. Microservices have hard boundaries, enforced by the network, at the cost of distributed systems. A modular monolith keeps the single deployable and gets its boundaries from code structure and tooling instead.
When it fits
| Fits when… | Doesn’t fit when… |
|---|---|
| A new product whose domain is still being learned | Parts need independent release cycles or independent scaling |
| Several developers or a few teams work on one product | Parts need different technology stacks |
| Modules have similar scaling and security needs | Many teams need full deployment autonomy |
| You want the option to extract services later, without paying for them now | The system is small enough that one well-layered codebase is plenty |
Martin Fowler’s advice is to start new projects as a monolith, learn where the boundaries are, and pay the “microservice premium” only once the complexity demands it.[10][11] Simon Brown’s version: if you cannot build a well-structured monolith, microservices will not save you.[12]
How it works
Modules are business capabilities
Shopify reorganised its Rails monolith away from the framework’s layout (models, views, controllers) into real-world domains such as orders, shipping, inventory and billing.[1] Grzybek’s definition makes the same point: a module delivers a complete capability, not a technical layer.[5]
Public contract, private internals
Spring Modulith describes a module in three parts: a provided interface (what it exposes, including events), an internal implementation, and a required interface (what it needs from others).[7] In .NET the equivalent is a small Contracts assembly per module, with everything else internal. Grzybek’s reference implementation lets modules reference only each other’s integration-events assemblies.[6]
Each module owns its data
Shopify set the rule that each component should exclusively own its data and counted cross-component associations as violations.[1] Grzybek’s implementation gives every module its own database schema;[6] in EF Core that is one DbContext per module with its own default schema.[16]
Communication through contracts or events
Modules call each other in-process through contract interfaces, or publish events. Spring Modulith records event publications in the same transaction and retries failed listeners, which works as an outbox.[9] Grzybek’s implementation is deliberately stricter: modules communicate only asynchronously, through an outbox and inbox per module.[6]
Boundaries must be enforced
- Spring Modulith verifies three rules: no cycles between modules, no access to another module’s internals, and only declared dependencies.[8]
- Shopify built Packwerk, a static checker for dependency and privacy violations that can record existing violations so only new ones fail the build.[3]
- .NET: project references and
internalare the first line of defence; architecture tests with NetArchTest catch the rest.[15]
Worked example: a configuration tool
An illustrative layout for a configuration product with four modules.
src/
Host/ConfigTool.Api // composition root; references *.Module and *.Contracts only
Modules/
Catalog/ Catalog.Contracts | Catalog.Module (schema "catalog")
Configurations/ Configurations.Contracts | Configurations.Module (schema "config")
Deployment/ Deployment.Contracts | Deployment.Module (schema "deploy")
Licensing/ Licensing.Contracts | Licensing.Module (schema "licensing")
BuildingBlocks/ // outbox, event bus abstraction; no business code
tests/
ArchitectureTests/ // module boundary rules
Catalog.IntegrationTests/ ... // one suite per module
[Fact]
public void Modules_only_reference_other_modules_through_contracts()
{
var result = Types.InAssembly(typeof(ConfigurationsModule).Assembly)
.ShouldNot()
.HaveDependencyOnAny("Catalog.Module", "Deployment.Module", "Licensing.Module")
.GetResult();
Assert.True(result.IsSuccessful);
}
Inside each module, choose the structure that fits: a Clean or hexagonal core for the rule-heavy Configurations module, vertical slices for the CRUD-like Catalog. Grzybek’s reference implementation uses Clean Architecture inside every module.[6]
Where it is used in practice
- Shopify. Work began in 2017; by 2020 there were 37 components and a dense dependency graph with many cycles.[2] The in-house tracing tool was replaced by Packwerk.[3] In 2024 the team removed privacy checks from Packwerk, found that zero violations did not mean a package could run in isolation, and concluded that the tool “is a sharp knife, and it must be wielded with care.”[4]
- Spring Modulith turns the style into a framework for Spring applications, with verification and module-level tests built in.[7]
- Kamil Grzybek’s reference implementation shows the full .NET version: schema per module, outbox and inbox, architecture tests, Clean Architecture inside modules.[6]
Gains and costs
What it buys
- One pipeline, one process. No network hops, simple transactions, easy refactoring across modules.
- Clear ownership. Errors and chores route to the owning module; Shopify used this to spread upgrade work.
- Replaceable parts. An isolated module can be rewritten or replaced behind its contract.
- A path to services. A clean module is the cheapest possible starting point for extraction.
- Low ceremony at the top. One repository, one build, one deployment.
What it costs
- Discipline and tooling, forever. Boundaries erode without checks in CI.
- Deployed and scaled together. One release train, one runtime, one technology stack.
- Boundary design is hard. Wrong cuts are cheaper to fix than in microservices, but still costly.
- False confidence. Passing static checks is not the same as being able to run a module alone.
How it goes wrong
- Shared tables. Two modules reading and writing the same table quietly undo every other boundary.[1]
- Hiding internals but keeping the tangle. Shopify found encapsulation useless while the dependency graph stayed dense and cyclic.[2]
- Modules drawn by layer or by name. Boundaries must follow function and capability, not folder names.[4]
- Over-prescriptive interface rules that make every call a ceremony.[2]
- Static checks as the only proof. Boot and run a module on its own from time to time.[4]
- Extracting a coupled module. Turning an entangled module into a service creates a real distributed monolith.
Testing a modular monolith
| Level | What | How |
|---|---|---|
| Architecture | Boundaries, no cycles, no internals access | Spring Modulith verify(), NetArchTest rules[8] |
| Module | One module with its neighbours stubbed | Spring’s module tests boot a single module; needing many beans from other modules is itself a coupling signal[17] |
| Use case | Each use case against a real database | Grzybek’s implementation mocks only external systems[6] |
| Contract | Each module’s public contract and event shapes | Tests owned by the providing module |
Origins
- 2015: Martin Fowler’s “MonolithFirst” and “MicroservicePremium” argue for starting monolithic, with careful modularity at both API and data level.[10][11]
- 2017 to 2024: Shopify’s componentisation of its core monolith, from component labels to Packwerk and its retrospective.[1][4]
- 2018 onward: Simon Brown’s talks popularise the term; Kamil Grzybek publishes the primer and .NET reference implementation in 2019.[12][5]
- 2023: the Prime Video debate brings monoliths back into the mainstream conversation.[13]
Key takeaways
- Split by business capability, not by technical layer; a module delivers a complete capability.
- Expose a small contract (interfaces, integration events) and keep everything else internal.
- Give every module its own data; shared tables silently undo every other boundary.
- Verify boundaries in CI and, from time to time, actually run a module alone: static checks alone give false confidence.
- Move a module to a service only when it needs independent deployment, scaling or technology.
Sources
- Deconstructing the Monolith: designing software that maximizes developer productivity · Shopify Engineering, 2019Primaryshopify.engineering
- Under Deconstruction: the state of Shopify's monolith · Shopify Engineering, 2020Primaryshopify.engineering
- Enforcing modularity inside a Rails monolith with Packwerk · Shopify Engineering, 2020Primaryshopify.engineering
- A Packwerk retrospective · Shopify Ruby team, 2024Primaryrailsatscale.com
- Modular Monolith: a primer · Kamil Grzybek, 2019Primarykamilgrzybek.com
- modular-monolith-with-ddd · Reference implementation in .NETReferencegithub.com
- Spring Modulith: fundamentalsReferencedocs.spring.io
- Spring Modulith: verifying module structureReferencedocs.spring.io
- Spring Modulith: working with application eventsReferencedocs.spring.io
- MonolithFirst · Martin Fowler, 2015Primarymartinfowler.com
- MicroservicePremium · Martin Fowler, 2015Primarymartinfowler.com
- Modular monolith · Simon BrownPrimarysimonbrown.je
- Monoliths are not dinosaurs · Werner Vogels, 2023Primaryallthingsdistributed.com
- Prime Video switched from serverless to EC2 and ECS to save costs · InfoQ, 2023Secondaryinfoq.com
- NetArchTestReferencegithub.com
- Entity types: table schema · EF Core documentationReferencelearn.microsoft.com
- Spring Modulith: integration testing application modulesReferencedocs.spring.io
Referenced by
- Which Architecture Style When? Comparison