Skip to content

HoangCaoPhi/practical-modular-monolith

Repository files navigation

🏆 Modular Monolith with DDD

🎯Introduction

📄 Document

🚀 Getting Started

The following prerequisites are required to set up, build, and run the solution efficiently:

  • 🟣 .NET 9.0 SDK (Latest version) – Provides the necessary runtime and development tools for building the application.

  • 🐳 Docker – Enables containerized deployment, ensuring a consistent and scalable runtime environment.

💻 How to Use Codebase

📌 Directory Structure

📦 src
 ┣ 📂 BuildingBlocks
 ┃ ┣ 📂 Application
 ┃ ┣ 📂 Domain
 ┃ ┣ 📂 Infrastructure
 ┃ ┣ 📂 IntegrationEvent
 ┃ ┣ 📂 Persistence
 ┃ ┣ 📂 Presentation
 ┃ ┗ 📂 SharedKernel
 ┣ 📂 Hosts
 ┃ ┣ 📂 docker-compose
 ┃ ┗ 📂 Web.Api
 ┣ 📂 Modules
 ┃ ┗ 📂 ModuleTemplate
 ┃   ┗ 📂 Application
 ┃      ┗ 📂 Abstractions
 ┃          ┗ 📂 Data 
 ┃              ┗ 📜 IReadDbContext, IReadRepository...
 ┃          ┗ 📂 Authentication
 ┃          ┗ 📂 Storage.... 
 ┃      ┗ 📂 EntityNames
 ┃          ┗ 📂 DomainHandlers 
 ┃          ┗ 📂 IntegrationEventHandlers  
 ┃          ┗ 📂 UseCases (Request, Response, Handler, DomainServiceImpl)   
 ┃   ┗ 📂 Endpoints
 ┃      ┗ 📜 IModuleEnpoint
 ┃      ┗ 📝 EntityNames  
 ┃   ┗ 📂 Infrastructure
 ┃      ┗ 📂 Consumers
 ┃      ┗ 📂 Persistence
 ┃          ┗ 📂 EntityConfiguration
 ┃          ┗ 📂 Contexts   
 ┃          ┗ 📂 Repositories    
 ┃          ┗ 📂 SeedData
 ┃      ┗ 📂 OtherInfra      
 ┃   ┗ 📂 Models
 ┃      ┗ 📂 EntityNames
 ┃          ┗ 📂 DomainEvents 
 ┃          ┗ 📜 IRepo  
 ┃          ┗ 📜 IDomainService   
 ┃          ┗ 📝 EntityErrors  
 ┃ ┗ 📂 Difference Module...
📦 tests
 ┣ 📂 IntegrationTests
 ┗ 📂 UnitTests
  • 🏛 BuildingBlocks

    • Contains foundational components shared across different modules.
  • 🌐 Hosts

    • Contains hosting-related configurations and services.
    • docker-compose: Docker configurations for service orchestration.
    • Web.Api: The main API gateway for handling client requests.
  • 🧩 Modules

    • Contains domain-specific features, structured as independent modules.
    • Each module encapsulates its own domain logic, ensuring separation of concerns.

🚀 Convention

Although we do not separate it into multiple layers, we still organize the folders according to Clean Architecture so that we can expand it later if needed.

We use feature modules for functionalities that are closely related and serve the same application domain. It makes sense to group them into a feature module. A feature module organizes code relevant to a specific feature, helping to maintain clear boundaries and improve organization. This is particularly important as the application or team grows, and it aligns with the SOLID principles.

Models:

Responsible for representing concepts of the business, information about the business situation, and business rules. State that reflects the business situation is controlled and used here, even though the technical details of storing it are delegated to the infrastructure

  • Each folder represents an aggregate root. Named in plural form. e.g. Orders, Products.

  • Each aggregate root folder might contain:

  • Root entity, and its child entities. Named in singular form, child entities are prefixed with the parent entity name. e.g. Order, OrderItem.

  • Errors specific to the aggregate root entity. Named in plural form. e.g. OrderErrors. OrderItemErrors

  • Domain events. Format {PastTenseVerb}{Noun}DomainEvent e.g. CreatedOrderDomainEvent

  • IRepository interface. Named in singular form. e.g. IOrderRepository

  • Value objects are placed here. Named in singular form. e.g. Money, Address.

Application:

Abstractions

With Dependency Inversion principle, allows you to decouple the infrastructure layer from the rest of the layers, which allows a better decoupled implementation of the DDD layers. Then, we Use Dependency Injection to injec infrastructure objects into your application layer

=> Contains interfaces that will be implemented in the Infrastructure layer to leverage IOC (Inversion of Control). Each feature is organized into its own dedicated directory for better structure and maintainability.

  • E.g: ExternalApi, Storage...
UseCases:
  • We will design the application while ensuring adherence to SOLID principles.
  • We will implement the CQRS pattern here. CQRS has two sides:
    • The first side is queries, utilizing simplified queries with the IReadDbContext.
    • The second side is commands, which serve as the starting point for transactions and the input channel from outside the service.
  • Each folder represents an entity and should be named in plural form, such as Orders or Products. Additionally, folders like DomainHandlers and IntegrationEventHandlers should be used to organize domain-specific handlers and integration event handlers, ensuring clear separation of concerns.
  • Each of the above folders can contain:
    • Command: A command is a request to change the system's state and should be processed only once. It follows an imperative naming style, often including a verb and the aggregate type (e.g., CreateOrderCommand). Commands are data structures (DTOs) that hold the necessary information for execution but contain no behavior. In C#, commands are typically represented as records. => Format {Verb}{Noun}Command

    • CommandHandler: The command handler is central to the application layer in CQRS and DDD but should not contain domain logic. Instead, domain logic belongs within aggregate roots, child entities, or domain services. The command handler follows SRP, handling a single aggregate and using domain events if multiple aggregates need updates. It retrieves the domain model, applies changes, and persists them via repositories. This approach ensures encapsulation, testability, and allows domain logic refactoring without modifying application or infrastructure layers. => Format {Verb}{Noun}CommandHandler.

    • Query: A request to retrieve data without modifying the system's state. It follows the read side of CQRS. => Format {Verb}{Noun}Query.

    • QueryHandler: Queries are idempotent and separate from domain rules. They can be implemented using Micro-ORMs like Dapper or IReadDbContext with EF Core, returning dynamic responses. Since queries do not modify data, they are not restricted by DDD patterns like aggregates, allowing greater flexibility in data retrieval. => Format {Verb}{Noun}QueryHandler.

    • Response: Queries return data needed by client applications using Data Transfer Objects (DTOs) called ViewModels. These ViewModels can aggregate data from multiple entities, tables, or aggregates, ignoring domain constraints for flexibility. This approach enhances productivity by allowing developers to query any necessary data. => ViewModels will be named {Verb}{Noun}{Response}.

    • Request: A class representing input from the client, typically used in API calls or command/query processing. => Format {Verb}{Noun}Request.

    • Mapping: Use mapping when there are more than 3 fields need to be mapped. Using IRegister interface from Mapster to register mappings. Mapping for Request -> Command/Query, Entity -> Response. => Format {Verb}{Noun}Mapping.

    • DomainEventHandler: => Format ``

    • IntegrationEventHander => Format ``

Endpoints:

  • Should describe the API response with Produces fluent API.
  • Should do the mapping from Request -> Command/Query here.

Infrastructure

  • Persistence/Repositories: This directory contains repositories, named following the format {EntityName}Repository. Repositories handle command-related operations such as Create, Update, and Delete, ensuring a clean separation of data access logic.

Mapping

  • Use mapping when there are more than 3 fields need to be mapped.
  • Using IRegister interface from Mapster to register mappings.
  • Mapping for Request -> Command/Query, Entity -> Response
  • Should be placed within the use case.

🚀 How to Create a Module 🛠️✨

Using the .NET Template Engine to Create a Project Template

If you haven't installed the template yet, navigate to the Modules folder (where you plan to run the commands) and install the template using the following command:

dotnet new install ../ .

Verify Template Installation

To check if the template was installed successfully, run:

dotnet new list | findstr modules

If the output includes your template, it means the installation was successful.

Create a New Module

To create a new module using this template, replace {ModuleName} with your desired name and run:

dotnet new modules -n {ModuleName}

This will generate a new module with the specified name.

Uninstall the Template

If you need to uninstall the template, run:

dotnet new uninstall ../ .

About

No description or website provided.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published