It is written using .net core 7.0. Here is the big picture of the project. Sooner I will upgrade to .net 8.0 The project started Aug 2023 and was completed in January 2024. Star it if you like it. Thanks!
Method | Request URI | Use Case |
---|---|---|
GET | api/v1/Catalog | Listing Products and Categories |
GET | api/v1/Catalog/{id} | Get Product with ProductId |
GET | api/v1/Catalog/GetProductByCategory/{category} | Get Products with Category |
POST | api/v1/Catalog | Create new Product |
PUT | api/v1/Catalog | Update Product |
DELETE | api/v1/Catalog/{id} | Delete Product |
Method | Request URI | Use Case |
---|---|---|
GET | api/v1/Basket | Get Basket and Items with username |
POST | api/v1/Basket | Update Basket and Items (Add remove item to basket) |
DELETE | api/v1/Basket/{id} | Delete Basket |
POST | api/v1/Basket/Checkout | Checkout Basket |
Method | Request URI | Use Case |
---|---|---|
GET | api/v1/Order | Get Orders with username |
POST | api/v1/Order | Update Basket and Items (Add remove item to basket) |
Tradition n-tier architecture:
User -> Presentation Layer (UI Component => Presentation Layer) -> Business Logic Layer (Service Layer => Business Logic Layer) -> Data Access Layer -> Database (Data Source)
- Data Access Layer: Only database operations are performed here, Add, Update, Delete, Get data from database.
- Business Logic Layer: Only business logic is implemented here, It will process data taken from the Data Access Layer and return it to the Presentation Layer. We dont use this layer directly, the data comes from user to the Presentation Layer and then to the Business Logic Layer, processed and send to data access layer and then to the database.
- Presentation Layer: This is the layer where the user interact. It can be a web application or API. The main purpose of this layer to show the data to the user and take the data from the user and send it to the Business Logic Layer.
API/Application Layer -> Domain Model Layer -> Infrastructure Layer
-
API/Application Layer: Entry point into the service. Exposes endpoints and enforces validations. It will be controller classes.
-
Domain Model Layer: Contains business rules and logic. Business operations are implemented here
-
Infrastructure Layer: Provides infrustructure plumbing. Primary responsibility is to presistance of business state.
It follows solid patterns. I has two purpose it is an abstration of data layer and it is a way to centralize handling the domain object. It is like a middle layer between the application and data access logic. It makes code easy to maintain and test.
- Includes CRUD operations
- Implemented Domain-Driven Design (DDD), CQRS, Clean Architecture,
- Used best practices and SOLID principles
- Developed CQRS implementation on commands and queries with using MediatR, FluentValidation and AutoMapper packages
- Entity Framework Core Code-First approach
- Consume basketCheckout event from RabbitMQ
- CQRS implements with triggering OrderCommand to insert order record to database
When design and build large complex microservice-based applications with multiple client applications, a good approch to consider is an API Gateway. This pattern is a service provides a single-entry point into the certain groups of microservices. It is similar to Facade pattern from object-oriented desing, in this case it is a part of a distributed system.
The API Gateway pattern is also known as the "backend for frontend" (BFF) because you build it while thinking about the needs of the client application. Therefore, it sits between the clients and the microservices. It acts as a reverse proxy, routing requests from clients to services. It can also provide other cross-cutting functionality such as authentication, SSL termination, and cache.
The API Gateway handles requests in one of two ways. Some requests are simply proxied/routed to the appropriate service. It handles other requests by fanning out to multiple services. used to handle requests by routing them to the appropriate backend service or by invoking multiple backend services and aggregating the results.
In the bellow example, the API Gateway would be implemented as a custom ASP.NET Core WebHost services. Usually it is not a good idea to have a single API Gateway aggregating all the internal microservices. If it does, it acts a monolithic aggregator or orchestrator and violates microservice autonomy by coupling all the microservices. Therefore, the API Gateways should be segregated by business boundaries and the cient applications and should not act as a single aggregator for all the internal microservices.
It is better to have multiple API Gateways, each one for a specific group of microservices. This way, the API Gateway is closer to the client application and can be optimized for its needs.
When splitting the API Gateway tier into multiple API Gateways, if the application has multiple client apps, there can be a primary pivot when identifying the multiple API Gateways types, so that you can have a different Facade for the needs of each client application. That means you can split to the requirements. As for the client into the custom API gateway with applying the BFF Backend for Frontend Pattern
It is a Lightweight API Gateway that recommended for simpler approches. Ocelot is an Open Source API Gateway built using .NET Core. It is aimed at microservices architectures where there is a need for a unified entry point into the system.
It is lightweight, fast, scalable and provides routing, filtering, caching, authentication, rate limiting and load balancing out of the box. The main reason to choose Ocelot for our reference application is because it is a lightweight .NET Core API Gateway. It is easy to use and configure. It is also open source and has a very active community. It can be deploy into the same application deployment environment where we deploy our microservices or containers such as Docker Host, Kubernetes and etc.
Ocelot is designed to work .NET Core only.
- Route Catalog APIs with /Catalog path
- Route Basket APIs with /Basket path
- Route Order APIs with /Order path
Method | Request URI | Use Case |
---|---|---|
GET/POST | /Catalog | Route /api/v1/Catalog APIs |
GET | /Catalog/{id} | Route /api/v1/Catalog APIs |
GET/POST | /Basket | Route /api/v1/Basket APIs |
POST | /Basket/Checkout | Route /api/v1/Basket APIs |
GET | /Order | Order /api/v1/Order APIs |
Using a gateway to aggregate multiple individual requests are made a single request.This pattern is useful when a client must make multiple calls to different backend systems to perform an operation.
In order to perform a single task, a client may have to make multiple calls to various backend services. An application that relies on many services to perform a task must expend resources on each request. For example, When any new feature or service is added to the application, additional requests are needed, further increasing resource requirements and network calls. This chattiness between a client and a backend can adversely impact the performance and scale of the application.
Microservice architectures have made this problem more common, as applications built around many smaller services naturally have a higher amount of cross-service calls. So, as a solution of that, use a gateway to reduce chattiness between the client and the services. The gateway can aggregate multiple requests into a single request, reducing the number of round trips between the client and the backend services.
Using chain http calls are not a good practice. It is better to use a gateway to aggregate multiple individual requests are made a single request.
docker pull mongo
to pull the mongo imagedocker run -d -p 27017:27017 --name shopping-mongo mongo
to run the mongo imagedocker logs -f shopping-mongo
to see the logs of the mongo imagedocker exec -it shopping-mongo bin/bash
to enter the mongo image
mongosh
to enter the mongo shellshow dbs
to show the databasesuse CatalogDb
to use the CatalogDb database and creating if not availableshow databases
to show the databasesshow collections
to show the collectionsdb.createCollection('Products')
to create the Products collectiondb.insertOne({name: 'Test', description: 'Test'})
to insert a document into the Products collectiondb.Products.find({})
to find all documents in the Products collectiondb.Products.find({}).pretty()
to find all documents in the Products collection in a pretty formatdb.Products.find({name: 'Test'}).pretty()
to find all documents in the Products collection with name equal to Test in a pretty formatdb.Products.find({name: 'Test', description: 'Test'}).pretty()
to find all documents in the Products collection with name equal to Test and description equal to Test in a pretty formatdb.Products.find({name: 'Test', description: 'Test'}).count()
to find the count of all documents in the Products collection with name equal to Test and description equal to Testdb.Products.insetMany([{name: 'Test1', description: 'Test1'}, {name: 'Test2', description: 'Test2'}])
to insert many documents into the Products collectiondb.Products.remove({})
to remove all documents in the Products collectiondb.Products.drop()
to drop the Products collectiondb.dropDatabase()
to drop the CatalogDb database
docker pull redis
to pull the redis imagedocker run -d -p 6379:6379 --name aspnetrun-redis redis
to run the redis imagedocker logs -f aspnetrun-redis
to see the logs of the redis imagedocker exec -it aspnetrun-redis redis-cli
to enter the redis imagedocker exec -it aspnetrun-redis bin/bash
to enter the redis image