sharof2000 / e-commerce-microservices

🛍️ A practical e-commerce Microservices based on Domain Driven Design, Vertical Slice Architecture, CQRS pattern, Event Driven Architecture.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ECommerce Microservices

ECommerce Microservices is a fictional eCommerce, based on different software architecture and technologies like Microservices Architecture, Vertical Slice Architecture, CQRS pattern, Domain Driven Design, Event Driven Architecture, Inbox and Outbox Pattern and using Postgres for write side and MongoDb for read side and etc.

For building blocks or cross cutting concerns part of this application, I used my micro-bootstrap library. it is has some infrastructure code for developing distributed applications.

For reading code easier, I used GitHub SubModule to create a link to microbootsrap library and use it here. You can also use, its Nuget Packages in your applications for easy to use.

This project is still in-progress and I update it to the latest technologies continuously.

Support ⭐

If you like my work, feel free to ⭐ this repository, and we will be happy together :)

Thanks a bunch for supporting me!

Table of Contents

The Goals of This Project

  • The Microservices Architecture with Domain Driven Design (DDD) implementation.
  • Correct separation of bounded contexts for each microservice.
  • Communications between bounded contexts through asynchronous Message Broker with using RabbitMQ with some autonomous services.
  • Simple CQRS implementation and Event Driven Architecture with using Postgres for Write Side and MongoDB and Elastic Search for Read Side. For syncing Read Side and Write Side I will use EventStore Projections or Marten Projections. we could also sync our Read and Write models with passing some integration event between services for achieving eventually consistency.
  • Implementing various type of testing like Unit Testing, Integration Testing and End-To-End Testing.
  • Using Inbox Pattern for guaranty message Idempotency for receiver microservice and Exactly-once Delivery pattern and using Outbox Pattern for ensuring about any message lost and At-Least one Delivery rule.
  • Using Best Practice and New Technologies and Design Patterns.
  • Using Event Storming for extracting data model and bounded context (using Miro).
  • Using Docker-Compose, Helm and Kubernetes for our deployment mechanism and Also using Terraform as infrastructure as a code.
  • Using Istio and Service Mesh for our mecroservices

Plan

This project is in progress, New features will be added over time.

High-level plan is represented in the table

Feature Status
Building Blocks Completed ✔️
API Gateway Completed ✔️
Identity Service Completed ✔️
Customer Service Completed ✔️
Catalog Service Completed ✔️
Order Service In Progress 👷‍♂️
Shipping Service Not Started 🚩
Payment Service Not Started 🚩

Technologies - Libraries

  • ✔️ .NET 6 - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core
  • ✔️ Npgsql Entity Framework Core Provider - Npgsql has an Entity Framework (EF) Core provider. It behaves like other EF Core providers (e.g. SQL Server), so the general EF Core docs apply here as well
  • ✔️ 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
  • ✔️ Polly - Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner
  • ✔️ Scrutor - Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection
  • ✔️ Opentelemetry-dotnet - The OpenTelemetry .NET Client
  • ✔️ DuendeSoftware IdentityServer - The most flexible and standards-compliant OpenID Connect and OAuth 2.x framework for ASP.NET Core
  • ✔️ Hangfire - Easy way to perform fire-and-forget, delayed and recurring tasks inside ASP.NET apps
  • ✔️ EasyCaching - Open source caching library that contains basic usages and some advanced usages of caching which can help us to handle caching more easier.
  • ✔️ AutoMapper - Convention-based object-object mapper in .NET.
  • ✔️ Hellang.Middleware.ProblemDetails - A middleware for handling exception in .Net Core
  • ✔️ IdGen - Twitter Snowflake-alike ID generator for .Net

The Domain And Bounded Context - Service Boundary

ECommerce Microservices is a simple e-commerce application that has the basic business scenario for online purchasing with some dedicated services. There are six possible Bounded context or Service for above business:

  • Identity Service: the Identity Service uses to authenticate and authorize users through a token. Also, this service is responsible for creating users and their corresponding roles and permission with using .Net Core Identity and Jwt authentication and authorization. I will add also Identity Server in future for this service. Each of Administrator, Customer and Supplier are a User, actually a IdentityUser. To be a User, User Registration is required. Each User is assigned one or more User Role. Each User Role has set of Permissions. A Permission defines whether User can invoke a particular action or not.

  • Catalog Service: The Catalog Service presents the ability to add items to our store, It can be electronics, foods, books or anything else. Items can be grouped into categories and catalogs. A catalog is defined as a list of items that a company showcases online. the catalog is a collection of items, which can be grouped into categories. An item can be assigned to only one category or be direct child of a catalog without any category. Buyer can browse the products list with supported filtering and sorting by product name and price. customer can see the detail of the product on the product list and in the detail page, can see a name, description, available product in the inventory,...

  • Customers Service: This service is responsible for managing our customers information, track the activities and subscribing to get notification for out of stock products

  • Order Service: The Orders Service main purpose is to store order details and manage orders created by users on client side. This service is not designed to be a full order processing system like ERP but serves as storage for customer orders details and can be synchronized with different external processing systems. Some of this service responsibilities are Saving orders, Saving order drafts, Ability to view and manage fulfillment, packages, Change discounts

  • Payment Service: The payment service is responsible for payment process of our customer with different payment process and managing and tracking our payment history

  • Shipping Service: The Shipping Service provides the ability to extend shipping provider list with custom providers and also provides an interface and API for managing these shipping providers. Some of shipping service capabilities are Register Shipping methods, Edit Shipping method, Shipment details, Shipping settings

Application Architecture

The bellow architecture shows that there is one public API (API Gateway) which is accessible for the clients and this is done via HTTP request/response. The API gateway then routes the HTTP request to the corresponding microservice. The HTTP request is received by the microservice that hosts its own REST API. Each microservice is running within its own AppDomain and has directly access to its own dependencies such as databases, files, local transaction, etc. All these dependencies are only accessible for that microservice and not to the outside world. In fact microservices are decoupled from each other and are autonomous. This also means that the microservice does not rely on other parts in the system and can run independently of other services.

Microservices are event based which means they can publish and/or subscribe to any events occurring in the setup. By using this approach for communicating between services, each microservice does not need to know about the other services or handle errors occurred in other microservices.

In this architecture we use CQRS Pattern for separating read and write model beside of other CQRS Advantages. Here for now I don't use Event Sourcing for simplicity but I will use it in future for syncing read and write side with sending streams and using Projection Feature for some subscribers to syncing their data through sent streams and creating our Custom Read Models in subscribers side.

Here I have a write model that uses a postgres database for handling better Consistency and ACID Transaction guaranty. beside o this write side I use a read side model that uses MongoDB for better performance of our read side without any joins with suing some nested document in our document also better scalability with some good scaling features of MongoDB.

For syncing our read side and write side we have 2 options with using Event Driven Architecture (without using events streams in event sourcing):

  • If our Read Sides are in Same Service, during saving data in write side I save a Internal Command record in my Command Processor storage (like something we do in outbox pattern) and after commenting write side, our command processor manager reads unsent commands and sends them to their Command Handlers in same corresponding service and this handlers could save their read models in our MongoDb database as a read side.

  • If our Read Sides are in Another Services we publish an integration event (with saving this message in the outbox) after committing our write side and all of our Subscribers could get this event and save it in their read models (MongoDB).

All of this is optional in the application and it is possible to only use what that the service needs. Eg. if the service does not want to Use DDD because of business is very simple and it is mostly CRUD we can use Data Centric Architecture or If our application is not Task based instead of CQRS and separating read side and write side again we can just use a simple CRUD based application.

Here I used Outbox for Guaranteed Delivery and can be used as a landing zone for integration events before they are published to the message broker .

Outbox pattern ensures that a message was sent (e.g. to a queue) successfully at least once. With this pattern, instead of directly publishing a message to the queue, we store it in the temporary storage (e.g. database table) for preventing missing any message and some retry mechanism in any failure (At-least-once Delivery). For example When we save data as part of one transaction in our service, we also save messages (Integration Events) that we later want to process in another microservices as part of the same transaction. The list of messages to be processed is called an OutboxMessages. Also we have a background service OutboxProcessorBackgroundService that periodically checks the our outbox messages in the database and try to send the messages to the broker with using our outbox service EfOutboxService. After it gets confirmation of publishing (e.g. ACK from the broker) it marks the message as processed to avoid resending. However, it is possible that we will not be able to mark the message as processed due to communication error, for example broker is unavailable. In this case our Outbox Background Service try to resend the messages that not processed and it is actually At-Least-Once delivery. We can be sure that message will be sent once, but can be sent multiple times too! That’s why another name for this approach is Once-Or-More delivery. We should remember this and try to design receivers of our messages as Idempotents, which means:

In Messaging this concepts translates into a message that has the same effect whether it is received once or multiple times. This means that a message can safely be resent without causing any problems even if the receiver receives duplicates of the same message.

For handling Idempotency and Exactly-once Delivery in receiver side, we could use Inbox Pattern.

In this is a pattern similar to Outbox Pattern. It’s used to handle incoming messages (e.g. from a queue) for unique processing of a single message only once (even with executing multiple time). Accordingly, we have a table in which we’re storing incoming messages. Contrary to outbox pattern, we first save the messages in the database, then we’re returning ACK to queue. If save succeeded, but we didn’t return ACK to queue, then delivery will be retried. That’s why we have at-least-once delivery again. After that, an inbox background process runs and will process the inbox messages that not processed yet. also we can prevent executing a message with specific MessgaeIdmultiple times. after executing our inbox message for example with calling our subscribed event handlers we send a ACK to the queue when they succeeded. (Inbox part of the system is in progress, this issue)

Also here I used RabbitMQ as my Message Broker for my async communication between the microservices with using eventually consistency mechanism. beside of this eventually consistency we have a synchronous call with using REST (in future I will use gRpc) for our immediate consistency needs.

We use a Api Gateway and here I used YARP that is microsoft reverse proxy (we could use envoy, traefik, Ocelot, ...), in front of our services, we could also have multiple Api Gateway for reaching BFF pattern. for example one Gateway for mobile apps, One Gateway for web apps and etc. With using api Gateway our internal microservices are transparent and user can not access them directly and all requests will serve through this Gateway. Also we could use gateway for load balancing, authentication and authorization, caching ,...

Application Structure

In this application I used a Mediator Pattern with using ICommandProcessor and IQueryProcessor (In top of MediatR library) as mediator gateway for sending command and query to our handlers. Also I use these mediators in my controllers for a clean and Thin Controller. instead of using and injecting some Application Service classes in our controller and depending to different services that breaks single responsibility principle in our controller and a bug bull of mode in our code, we just inject a mediator class which is responsible for routing to corresponding handler for handling logic.

Mediator mediate between objects and prevent direct coupling between objects, and objects could communicate each other with sending some message through mediator as a gateway. Here We use mediator pattern to manage the delivery of messages to handlers. For example in our controllers we create a command and send it to mediator and mediator will route our command to a specific command handler in application layer.

One of the advantages behind the mediator pattern is that it allows us to define some pipelines of activities for requests on top of our mediator for doing some cross cutting concerns that brings Single Responsibility Principle and Don't Repeat Yourself principles in our application.

For implementing these pipelines in our mediator to handle cross-cutting concerns we use MediatR libraries and its Pipeline Behaviors or we can create some MediatR Decorators as our pipelines.

Also in this project I used vertical slice architecture or Restructuring to a Vertical Slice Architecture also I used feature folder structure in this project.

  • We treat each request as a distinct use case or slice, encapsulating and grouping all concerns from front-end to back.
  • When We adding or changing a feature in an application in n-tire architecture, we are typically touching many different "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, we couple vertically along a slice and each change affects only one 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. For implementing vertical slice architecture using cqrs pattern is a good match.

Also here I used CQRS for decompose my features to very small parts that makes our application:

  • maximize performance, scalability and simplicity.
  • adding new feature to this mechanism is very easy without any breaking change in other part of our codes. New features only add code, we're not changing shared code and worrying about side effects.
  • easy to maintain and any changes only affect on one command or query (or a slice) and avoid any breaking changes on other parts
  • it gives us better separation of concerns and cross cutting concern (with help of mediatr behavior pipelines) in our code instead of a big service class for doing a lot of things.

With using CQRS, our code will be more aligned with SOLID principles, especially with:

  • Single Responsibility rule - because logic responsible for a given operation is enclosed in its own type.
  • Open-Closed rule - because to add new operation you don’t need to edit any of the existing types, instead you need to add a new file with a new type representing that operation.

We cut each business functionality into some vertical slices, and inner each of this slices we have technical folders structure specific to that feature (command, handlers, infrastructure, repository, controllers, ...). In Our CQRS pattern each command/query handler is a separate slice. This is where you can reduce coupling between layers. Each handler can be a separated code unit, even copy/pasted. Thanks to that, we can tune down the specific method to not follow general conventions (e.g. use custom SQL query or even different storage). In a traditional layered architecture, when we change the core generic mechanism in one layer, it can impact all methods.

For checking validation rules we use two type of validation:

  • Data Validation: Data validation verify data items which are coming to our application from external sources and check if theirs values are acceptable but Business rules validation is a more broad concept and more close to how business works and behaves. So it is mainly focused on behavior For implementing data validation I used FluentValidation library for cleaner validation also better separation of concern in my handlers for preventing mixing validation logic with orchestration logic in my handlers.
  • Business Rules validation: I explicitly check all of the our business rules, inner my handlers and I will throw a customized exception based on the error that these errors should inherits from AppException class, because of these exceptions, occurs in application layer and we catch this exceptions in api layer with using Hellang.Middleware.ProblemDetails middleware and pass a correct status code to client.

Prerequisites

  1. Install git - https://git-scm.com/downloads.
  2. Install .NET Core 6.0 - https://dotnet.microsoft.com/download/dotnet/6.0.
  3. Install Visual Studio 2022, Rider or VSCode.
  4. Install docker - https://docs.docker.com/docker-for-windows/install/.
  5. Make sure that you have ~10GB disk space.
  6. Clone Project https://github.com/mehdihadeli/e-commerce-microservices, make sure that's compiling
  7. Open ECommerce.sln solution.

How to Run

For Running this application we could run our microservices one by one in our Dev Environment, for me, it's Rider, Or we could run it with using Docker-Compose or we could use Kubernetes.

For testing apis I used REST Client plugin of VSCode its related file scenarios are available in _httpclients folder. also after running api you have access to swagger open api for all microservices in /swagger route path.

In this application I use a fake email sender with name of ethereal as a SMTP provider for sending email. after sending email by the application you can see the list of sent emails in ethereal messages panel. My temp username and password is available inner the all of appsettings file

Using Docker-Compose

  1. Go to deployments/docker-compose/docker-compose.yaml and run: docker-compose up.
  2. Wait until all dockers got are downloaded and running.
  3. You should automatically get:

Some useful docker commands:

// start dockers
docker-compose -f .\docker-compose.yaml up

// build without caching
docker-compose -f .\docker-compose.yaml build --no-cache

// to stop running dockers
docker-compose kill

// to clean stopped dockers
docker-compose down -v

// showing running dockers
docker ps

// to show all dockers (also stopped)
docker ps -a

Using Kubernetes

TODO

Contribution

The application is in development status. You are feel free to submit pull request or create the issue.

License

The project is under MIT license.

About

🛍️ A practical e-commerce Microservices based on Domain Driven Design, Vertical Slice Architecture, CQRS pattern, Event Driven Architecture.


Languages

Language:C# 98.0%Language:Dockerfile 1.4%Language:Batchfile 0.5%Language:Shell 0.1%