MODULAR
MONOLITH ARCHITECTURE
What we’ll cover
Software Architectures
Standard Monoliths
Microservices
Modular Monoliths
1
2
3
Standard Monoliths
Monolithic architecture is a traditional software development style where all components of an application are tightly integrated and share a single codebase. In a monolithic architecture, the application is built as a single, unified unit, which typically includes the user interface, business logic, and data access layers all bundled together.
Microservices
Microservices architecture is a design approach where an application is built as a collection of loosely coupled, independently deployable services. Each service is responsible for a specific piece of the application's functionality and communicates with other services through well-defined APIs.
A modular monolith is a software architecture that combines the benefits of both monolithic and modular design principles. It retains the simplicity of a monolithic application while organizing the codebase into distinct, loosely coupled modules. Each module represents a specific domain or feature and encapsulates its functionality.
Modular Monoliths
| Standard Monolith | Micro services | Modular Monolith |
Modularity | | | |
Cost | | | |
Scalability | | | |
Simplicity | | | |
Software Architecture Trade-offs
Everything is a trade-off, there is no solution that fits for all.
The First Law of Software Architecture
Module Definition Rules
Module Communication in C#
1
2
4
Modular Monoliths
Host Application
Module Implementation in C#
3
Module Definition Rules
Users Module
The host application in a modular monolith is the central application that loads, configures, and runs all the modules. It is the entry point of the application, responsible for initializing the application environment, managing dependencies, and handling the lifecycle of modules.
Host Application
Mediator Pattern
Module Endpoints Registration
Domain & Integration Events
Importing Modules to Host
Public API’s
1
4
2
5
3
Module Implementations in C#
Mediator Pattern
The Mediator pattern manages communication between objects through a central mediator, preventing direct interactions. This reduces dependencies and makes inter-module communication more organized. In C#, MediatR library implements this pattern to handle commands, queries, and events. MediatR is a great choice in modular monolith applications because it simplifies inter-module communication through a single MediatR object and serves as an in-memory event bus, enabling flexible and decoupled communication between modules.
Public API’s
In modular monolith applications, modules communicate with each other through public APIs. Although modules cannot directly reference one another, a separate project with types. These types can be freely utilized by other modules to perform tasks like sending queries, firing events, or listening for specific events. This approach maintains clear boundaries between modules while allowing for flexible and efficient inter-module communication.
In modular monolith applications, domain events are handled within the module itself, so they don’t need to be shared via the public API. However, the types for integration events should be included in the public API. We use MediatR as an in-memory event bus to manage these events efficiently.
Domain & Integration Events
We have Mediator pattern for handling module communications in various ways. In the Host application, we would need to register all of the endpoints of an module. This requires some helper tools or our own implementation.
Module Endpoints Registration
FastEndpoints
Carter
1
2
FastEndpoints is a lightweight .NET library that simplifies the creation of minimal APIs. It allows developers to define endpoints using a class-based approach, where each endpoint is represented by a class inheriting from Endpoint<TRequest, TResponse>. This framework automatically discovers and registers these endpoints, handling routing, request/response handling, validation, and more, making API development fast and straightforward.
FastEndpoints
Carter is a library designed for ASP.NET Core applications that simplifies and modularizes the creation of API endpoints. It provides an easy and structured approach for defining minimal APIs, making your application more manageable and organized. API endpoints can also have model binding, response customization, validation support. You can also use carter middlewares for your use cases. Carter is based on modularity.
Carter
Each module should have it’s own method to register its services.
Importing Modules to Host
Direct Synchronous Calls
The Mediator Pattern
The Outbox Pattern
Message Brokers
Materialized Views
1
2
4
3
5
Module Communication Patterns
Direct synchronous calls can be in many ways.
Direct Synchronous Calls
The Mediator Pattern
The MediatR library allows us to communicate between modules without any other dependency or injection. In the example below, the AddItemToCartHandler is trying to get book details from the Books module. Since we have an public contract object and a mediator, everything is fine.
Message brokers can be used in order to communicate with modules. All of the modules can handle events, commands and queries based on the requirements.
We can use RabbitMQ with MassTransit library to execute a query to Users module from Order Processing module.
Message Brokers
Materialized views are effective for optimizing data retrieval in a modular monolith, but challenges arise when a module evolves into a microservice with its own database. Relying on database-level materialized views can create complications if data storage changes. To solve this, the application should handle data retrieval, making the storage type or location irrelevant. Redis is an excellent choice for this, allowing efficient caching and access to precomputed data, ensuring the system remains flexible and performant as the architecture evolves.
Materialized Views
Materialized Views
Architecture Tests
Module to Microservice
1
2
Bonus Content
Architecture Tests
Architecture tests in a modular monolith are valuable for ensuring that modules remain independent and adhere to established design principles. Using the ArchUnit.Net library, you can enforce rules to verify that no modules depend on each other and that the overall architecture aligns with your defined standards.
Module to Microservice
A modular monolith combines the strengths of both monolithic and microservice architectures, balancing simplicity with modularity. However, when the time comes to separate a specific module and turn it into an independent microservice, several crucial steps must be taken:
Resources for Modular Monoliths
Teşekkürler!