Introducing Modulith: A Tool for Modular Monoliths
If you’ve been following software architecture trends, you know that microservices often steal the spotlight. However, for many teams, a modular monolith can offer a more straightforward yet still powerful approach to building large applications. But if you’re tired of wrestling with complex microservices or monolithic architectures that quickly turn into a maintenance nightmare, Modulith—an open-source project designed to streamline the development of modular monoliths—is here to change the game.
What’s a Modular Monolith and Why Should You Care?
A modular monolith is a single application divided into distinct, well-defined modules, each encapsulating a specific functional area of the application. This structure makes the codebase easier to understand, maintain, and test. Unlike a tightly coupled “Big Ball of Mud” monolith, a modular monolith offers clear boundaries and separation of concerns without the overhead of managing distributed systems like microservices.
While microservices achieve boundaries through physical separation, a modular monolith avoids operational overhead by using a single solution for all modules. As Ardalis teaches in his Modular Monoliths course, you can use .NET projects and C# access modifiers to separate a module’s public interface (Contracts) from its internal types. This ensures that other modules can only reference the public interface of a module.
A Practical Example
Imagine you’re developing an e-commerce application called eShop. Initially, it starts as a single project, but as you add more features, it could become a tangled web of dependencies—a classic “Big Ball of Mud.” To avoid this, you decide to transform eShop into a modular monolith, starting with the Payments and Shipments modules.
First, we create a separate project for the Payments module internals and declare all its types as internal
. This way, other modules will not be able to reference the module’s internal types.
Then, we create a Contracts
project to define the public interface so that other modules can depend on our Contracts
module and not be concerned with our internal project. This ensures that references between modules are only introduced through well-defined, public interfaces, enforcing clear boundaries and eliminating the risk of unwanted dependencies.
We can also extend modularity to our tests by introducing a single test project per module. This way, we can organize our tests to target a specific module and end up with the following solution structure:
eShop/
├── eShop.EntryPoint <-- Original Project (Contains the program.cs, appsettings, etc.)
└── eShop.PaymentsModule
├── eShop.Payments.Internal
├── eShop.Payments.Contracts
├── eShop.Payments.Tests
The next step is to set the appropriate project dependencies.
-
The
Entry
project should reference theInternal
project of every module to ensure endpoints and services are properly registered in the application builder. -
The
Internal
project should also reference theContracts
project so that other modules can depend on the types it exposes. -
The
Test
project should depend on theInternal
project it’s meant to test. -
If another module, like
Shipments
, needs to communicate with thePayments
module, it would reference theeShop.Payments.Contracts
project.
Adding these references and supposing we have that second Payments
module will lead to a solution like this:
Now, we’ve set the path for eShop to become a maintainable and scalable application, all within a single solution and repository. If you want to learn more about this and other useful patterns when building Modular Monoliths check out Ardalis’ course. However, creating three projects per module manually is time-consuming and can discourage team members from adding new modules. How can we make this process faster and repeatable for everyone working on eShop?
Enter Modulith
Developed in conjunction with Ardalis, Modulith automates much of the setup you’d typically have to do manually when building modular monoliths.
With Modulith, you can add a new module to your project with a single command. It takes care of creating the necessary projects, adding the correct dependencies, and registering services for you. This allows you to focus on what really matters: building features. This is especially useful when working in a team. Modulith provides an easy way to set up new modules without worrying about the repetitive tasks involved in setting them up.
You can install Modulith by running:
dotnet new install Ardalis.Modulith
Creating a new modular solution is as simple as running:
dotnet new modulith -n eShop --with-module Payments
This creates a solution, eShop, with a single module, Payments. Then you can add new modules by running:
cd eShop # Change directory into the solution folder
dotnet new modulith --add basic-module --with-name Shipments --to eShop
That’s it! Now you have a solution with two modules. Then, you can add new modules by running the last command and changing the --with-name
parameter to the name of your new module. But what exactly was created?
Glad you asked. These three commands created eight projects and added the correct references between them. The resulting solution structure looks like this:
eShop/
├── eShop <-- Entrypoint
├── eShop.Shared
├── Payments
| ├── eShop.Payments <- Internal project
| ├── eShop.Payments.Contracts
| ├── eShop.Payments.Tests
├── Shipments
├── eShop.Shipments <- Internal project
├── eShop.Shipments.Contracts
├── eShop.Shipments.Tests
Stay Tuned for More
Modulith is not stopping at creating entire modules. A second set of features will focus on item templates. These templates don’t create whole modules but instead add a set of classes to help you implement specific patterns within your modules. For instance, one such item template could generate a set of CRUD endpoints and a repository for a new domain entity. Another template might create the classes needed for a materialized view for inter-module communication. While you’ll still need to tailor the generated code to your specific needs, the initial creation of classes will be incredibly fast and consistent, reducing repetitive tasks and encouraging team members to reuse known patterns.
Ready for more? Head over to the Modulith GitHub repository to get started. Your feedback is invaluable—whether you’re contributing a new module, reporting a bug, or sharing your success story.
This is just the beginning. In our upcoming posts, we’ll dive deeper into advanced features of Modulith, including custom templates for UI, Domain-Driven Design, and item templates.