/ecommerce-monolith

A monolith, built with .Net, DDD, CQRS, Vertical Slice Architecture, Event-Driven Architecture, and the latest technologies.

Primary LanguageC#MIT LicenseMIT

🛒 ECommerce-Monolith

ci-status

The primary objective of this project is to establish a framework that can facilitate the deployment and operation of a straightforward ECommerce application using cutting-edge technologies and architecture such as Vertical Slice Architecture, CQRS, and DDD in .Net. The primary focus of this project is not centered on business concerns. 🚀

Open in Gitpod

Table of Contents

The Goals of This Project

  • ❇️ Implementing Vertical Slice Architecture at the architecture level to create a scalable and maintainable structure for the application.
  • ❇️ Using Domain Driven Design (DDD) for implementing business processes and validation rules.
  • ❇️ Adopting CQRS implementation with the MediatR library for better separation of write and read operations.
  • ❇️ Implementing MediatR to reduce coupling and provide support for managing cross-cutting concerns within pipelines, including validation and transaction handling for the application.
  • ❇️ Using SqlServer as our relational database management system at the database level.
  • ❇️ Incorporating Unit Testing, Integration Testing, and End To End Testing for testing level to ensure the robustness and reliability of the application.
  • ❇️ Utilizing Fluent Validation and a Validation Pipeline Behaviour on top of MediatR to validate requests and responses and ensure data integrity.
  • ❇️ Using Minimal API for all endpoints to create a lightweight and streamlined API.
  • ❇️ Using Docker-Compose for our deployment mechanism to enable easy deployment and scaling of the application.

Technologies - Libraries

  • ✔️ .NET 7 - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core
  • ✔️ MVC Versioning API - Set of libraries which add service API versioning to ASP.NET Web API, OData with ASP.NET Web API, and ASP.NET Core
  • ✔️ EF Core - Modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations
  • ✔️ MediatR - Simple, unambitious mediator implementation in .NET.
  • ✔️ FluentValidation - Popular .NET validation library for building strongly-typed validation rules
  • ✔️ Swagger & Swagger UI - Swagger tools for documenting API's built on ASP.NET Core
  • ✔️ Serilog - Simple .NET logging with fully-structured events
  • ✔️ Scrutor - Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection
  • ✔️ AutoMapper - Convention-based object-object mapper in .NET
  • ✔️ NewId - NewId can be used as an embedded unique ID generator that produces 128 bit (16 bytes) sequential IDs
  • ✔️ Sieve - Sieve is a framework for .NET Core that adds sorting, filtering, and pagination functionality out of the box
  • ✔️ xUnit.net - A free, open source, community-focused unit testing tool for the .NET Framework
  • ✔️ Respawn - Respawn is a small utility to help in resetting test databases to a clean state
  • ✔️ Testcontainers - Testcontainers for .NET is a library to support tests with throwaway instances of Docker containers
  • ✔️ Bogus - Bogus is a simple fake data generator for .NET

Structure of Project

In this project I used vertical slice architecture and feature folder structure to structure my files.

To reduce coupling in our code, we leverage Mediatr and build pipelines on top of it to handle validation, logging, and transactions. Our domain follows Domain-Driven Design principles and employs value objects for business logic. We also incorporate validation into our business processes. When we complete work within our domain, it raises a domain event. Depending on the requirements, we can then react to this event and take appropriate action to further our business goals.

I treat each request as a distinct use case or slice, encapsulating and grouping all concerns from front-end to back with vertical slice architecture. In traditional approach like clean architecture, When adding or changing a feature in an application in n-tire architecture, we are typically touching many layers in an application. We are changing the user interface, adding fields to models, modifying validation, and so on. Instead of coupling across a layer in traditional architecture, we couple vertically along a slice. We minimize coupling between slices, and maximize coupling in a slice.

With this approach, each of our vertical slices can decide for itself how to best fulfill the request. New features only add code, we're not changing shared code and worrying about side effects.

In traditional ASP.net controllers, related action methods are usually grouped in one controller. However, in my recent project, I opted to use the REPR pattern (Route-Endpoint-Presenter-Resource) design pattern instead. With this pattern, each action is given its own small endpoint, consisting of a route, the action, and an IMediator instance (see MediatR), which is handled by a request-specific IRequestHandler to perform business logic before returning the result. This approach not only separates the action logic into individual handlers, but it also supports the Single Responsibility, Open Close Principle and Don't Repeat Yourself principles, resulting in clean and thin controllers.

To achieve better separation of concerns and cross-cutting concerns, I used the Mediator pattern in combination with CQRS (Command Query Responsibility Segregation) to decompose features into small, vertical slices. Each slice has a group of classes specific to that feature, including command, handlers, infrastructure, repository, and controllers. By grouping them together, we can easily maximize performance, scalability, and simplicity, as well as maintain and add features without creating breaking changes or side effects.

With CQRS, we can reduce coupling between layers and tune down specific methods to not follow general conventions. This is achieved by cutting each business functionality into vertical slices, where each command/query handler is a separate slice. As a result, each handler can be a separate code unit, even copy/pasted, allowing us to customize individual methods as needed. In contrast, in a traditional layered architecture, changing the core generic mechanism in one layer can impact all methods, which can be time-consuming and challenging to maintain.

Overall, by using the REPR pattern and CQRS with the Mediator pattern, we can create a better-structured and more maintainable application, with improved separation of concerns.

How to Run

Docker Compose

Run this app in docker using the docker-compose.yml file with the below command at the root of the application:

docker-compose -f ./deployments/docker-compose/docker-compose.yml up -d

Support

If you like my work, feel free to:

  • ⭐ this repository. And we will be happy together :)

Thanks a bunch for supporting me!

Contribution

Thanks to all contributors, you're awesome and this wouldn't be possible without you! The goal is to build a categorized, community-driven collection of very well-known resources.

Please follow this contribution guideline to submit a pull request or create the issue.

License

This project is made available under the MIT license. See LICENSE for details.