Microservices architecture, or simply microservices, comprises a set of focused, independent, autonomous services that make up a larger business application. The architecture provides a framework for independently writing, updating, and deploying services without disrupting the overall functionality of the application. Within this architecture, every single service within the microservices architecture is self-contained and implements a specific business function. For example, building an e-commerce application involves processing orders, updating customer details, and calculating net prices. The app will utilize various microservices, each designed to handle specific functions, working together to achieve the overall business objectives.
Adopting a microservices architecture brings a range of benefits that can transform how organizations build and operate software. One of the primary advantages is the ability to scale individual services independently, which helps optimize resource usage and eliminates bottlenecks that can affect the entire application. This independent scalability also means that development teams can deploy services independently, reducing the risk of system-wide outages and enabling continuous delivery of new features. Deployments can be performed with one service at a time or span multiple services, depending on the specific needs of the deployment.
I have implemented below features.
- NET 8.0 Web API application
- REST API principles, CRUD operations
- SQL Server database (MicroShop) connection
- Repository Pattern Implementation
- Swagger Open API implementation
- Consume Events (BasketCheckoutConsumer)
- Publish OrderCreateEvent event with using MassTransit and RabbitMQ
- Implementing DDD, CQRS, and Clean Architecture with using Best Practices
- Developing CQRS with using MediatR, FluentValidation and AutoMapper packages
- Consuming RabbitMQ BasketCheckout Event Queue with using MassTransit-RabbitMQ Configuration
- SQL Server Database connection
- Using Entity Framework Core ORM and migratation to SQL Server Manually.
- NET 8.0 Grpc Server application
- Build a Highly Performant inter-service gRPC Communication with Basket Microservice
- Exposing Grpc Services with creating Protobuf messages
- Using ADO.Net implementation to simplify data access and ensure high performance
- SQL Server database (MicroShopDiscount) connection
- NET 8.0 Web API application
- REST API principles, CRUD operations
- Redis database connection
- Consume Discount Grpc Service for inter-service sync communication to calculate product final price
- Publish BasketCheckout event with using MassTransit and RabbitMQ
- NET 8.0 Web API application
- Consume Events (ProcessInventoryConsumer)
- SQL Server database (MicroShop) connection
- Repository Pattern Implementation
- Swagger Open API implementation
- Publish InventorySuccessEvent event with using MassTransit and RabbitMQ
- NET 8.0 Web API application
- Consume Events (ProcessPaymentConsumer)
- SQL Server database (MicroShopPayment) connection
- Repository Pattern Implementation
- Swagger Open API implementation
- Publish PaymentSucceededEvent event with using MassTransit and RabbitMQ
- Implement API Gateways with Ocelot
- Sample microservices/Consul to reroute through the API Gateway
- The Gateway aggregation pattern in MicroShop.Aggregator
- Sync inter-service gRPC Communication
- Async Microservices Communication with RabbitMQ Message-Broker Service
- Using RabbitMQ Publish/Subscribe Topic Exchange Model
- Using MassTransit for abstraction over RabbitMQ Message-Broker system
- Publishing BasketCheckout event queue from Basket microservices and Subscribing this event from Ordering microservices (BasketCheckoutConsumer) and the rest of the ordering flow with Masstransit Saga State Machine (MassTransitStateMachine : OrderStateMachine)
- Create RabbitMQ EventBus.Messages library and add references Microservices
- Implementing Centralized Logging with SeriLog for Microservices
- Use the Consul HealthChecks feature in back-end ASP.NET microservices
- Making Microservices more resilient Use IHttpClientFactory to implement resilient HTTP requests
- Implement Retry patterns with MassTransit UseMessageRetry
- Implementing Consul Cluster with a server and a client agent for Service Discovery
- Register each service to mentioned Consul nodes with Hostnames
- Using Steeltoe library to register and resolve the services
Transitions between these states are triggered by user actions or system processes (Saga), providing a clear overview of the customer journey from initial product search to final purchase and order confirmation.
public class OrderStateMachine : MassTransitStateMachine<OrderState>
{
public State ProcessingInventory { get; set; }
public State ProcessingPayment { get; set; }
public State ProcessingEnd { get; set; }
public State Canceled { get; set; }
public Event<BasketCheckoutEvent> BasketCheckoutEvent { get; set; }
public Event<OrderCreateEvent> OrderCreateEvent { get; set; }
public Event<InventorySuccessEvent> InventorySuccessEvent { get; set; }
public Event<InventoryFailedEvent> InventoryFailedEvent { get; set; }
public Event<PaymentSucceededEvent> PaymentSucceededEvent { get; set; }
public Event<PaymentFailedEvent> PaymentFailedEvent { get; set; }
public Event<ProcessEndedEvent> ProcessEndedEvent { get; set; }
public OrderStateMachine()
{
InstanceState(x => x.CurrentState);
Event(() => BasketCheckoutEvent, e => e.CorrelateBy((s, c) => s.CorrelationId == c.Message.CorrelationId));
Event(() => OrderCreateEvent, e => e.CorrelateBy((s, c) => s.CorrelationId == c.Message.CorrelationId));
Event(() => InventorySuccessEvent, e => e.CorrelateBy((s, c) => s.CorrelationId == c.Message.CorrelationId));
Event(() => InventoryFailedEvent, e => e.CorrelateBy((s, c) => s.CorrelationId == c.Message.CorrelationId));
Event(() => PaymentSucceededEvent, e => e.CorrelateBy((s, c) => s.CorrelationId == c.Message.CorrelationId));
Event(() => PaymentFailedEvent, e => e.CorrelateBy((s, c) => s.CorrelationId == c.Message.CorrelationId));
Event(() => ProcessEndedEvent, e => e.CorrelateBy((s, c) => s.CorrelationId == c.Message.CorrelationId));
//*******************************************************************
// مرحله 1: ایجاد رکورد سفارش
Initially(
When(OrderCreateEvent)
.ThenAsync(async c =>
{
c.Instance.CorrelationId = c.Data.CorrelationId; // CorrelationId
c.Instance.OrderId = c.Data.OrderId;
c.Instance.CustomerId = c.Data.CustomerId;
c.Instance.Created = c.Data.CreationDate;
Log.Information($"Order {c.Data.OrderId} Created by CustomerId : {c.Data.CustomerId}");
})
.Activity(x => x.OfType<GenericOrderEventActivity<OrderCreateEvent>>())
.PublishAsync(async c => new ProcessInventory()
{
OrderId = c.Message.OrderId,
CustomerId = c.Message.CustomerId,
CorrelationId = c.Message.CorrelationId,
Created = c.Message.Created
})
.TransitionTo(ProcessingInventory)
.Then(c => Log.Information($"[Saga] Transitioned to ProcessingInventory for OrderId={c.Instance.OrderId}, CorrelationId={c.Instance.CorrelationId}"))
);
// مرحله 2: بررسی موجودی
During(ProcessingInventory,
When(InventorySuccessEvent)
.ThenAsync(async c =>
{
c.Instance.CorrelationId = c.Data.CorrelationId; // CorrelationId
c.Instance.OrderId = c.Data.OrderId;
c.Instance.CustomerId = c.Data.CustomerId;
c.Instance.Created = c.Data.CreationDate;
Log.Information($"Inventory reserved for Order {c.Data.OrderId}");
})
.Activity(x => x.OfType<GenericOrderEventActivity<InventorySuccessEvent>>())
// انتقال مرحله پرداخت خودکار به دستی و توسط دکمه پرداخت
.TransitionTo(ProcessingPayment)
.Then(c => Log.Information($"[Saga] Transitioned to ProcessingPayment for OrderId={c.Instance.OrderId}, CorrelationId={c.Instance.CorrelationId}")),
When(InventoryFailedEvent)
.ThenAsync(async c =>
{
c.Instance.CorrelationId = c.Data.CorrelationId; // CorrelationId
c.Instance.CancelReason = c.Data.Reason;
Log.Information($"Inventory reservation failed for Order {c.Data.OrderId}: {c.Data.Reason}");
})
.Activity(x => x.OfType<GenericOrderEventActivity<InventoryFailedEvent>>())
.TransitionTo(Canceled)
.Then(c => Log.Information($"[Saga] Transitioned to Canceled for OrderId={c.Instance.OrderId}, CorrelationId={c.Instance.CorrelationId}"))
);
// مرحله 3: پرداخت موفق
During(ProcessingPayment,
When(PaymentSucceededEvent)
.ThenAsync(async c =>
{
c.Instance.CorrelationId = c.Data.CorrelationId; // CorrelationId
c.Instance.OrderId = c.Data.OrderId;
c.Instance.CustomerId = c.Data.CustomerId;
c.Instance.Created = c.Data.CreationDate;
Log.Information($"Payment done for Order {c.Data.OrderId}");
})
.Activity(x => x.OfType<GenericOrderEventActivity<PaymentSucceededEvent>>())
.PublishAsync(async c => new ProcessEnd()
{
OrderId = c.Message.OrderId,
CustomerId = c.Message.CustomerId,
CorrelationId = c.Message.CorrelationId,
Created = c.Message.Created
})
.Then(c => Log.Information($"[Saga] Transitioned to ProcessingEnd for OrderId={c.Instance.OrderId}, CorrelationId={c.Instance.CorrelationId}"))
.TransitionTo(ProcessingEnd),
When(PaymentFailedEvent)
.ThenAsync(async c =>
{
c.Instance.CorrelationId = c.Data.CorrelationId; // CorrelationId
c.Instance.CancelReason = c.Data.Reason;
Log.Information($"Payment failed for Order {c.Data.OrderId}: {c.Data.Reason}");
})
.Activity(x => x.OfType<GenericOrderEventActivity<PaymentFailedEvent>>())
.TransitionTo(Canceled)
.Then(c => Log.Information($"[Saga] Transitioned to Canceled for OrderId={c.Instance.OrderId}, CorrelationId={c.Instance.CorrelationId}"))
);
// مرحله 4: end
During(ProcessingEnd,
When(ProcessEndedEvent)
.ThenAsync(async c =>
{
c.Instance.CorrelationId = c.Data.CorrelationId; // CorrelationId
c.Instance.OrderId = c.Data.OrderId;
Log.Information($"Ending done for Order {c.Data.OrderId}");
})
.Activity(x => x.OfType<GenericOrderEventActivity<ProcessEndedEvent>>())
.TransitionTo(Shipped)
.Then(c => Log.Information($"[Saga] Transitioned to Shipped for OrderId={c.Instance.OrderId}, CorrelationId={c.Instance.CorrelationId}"))
.Finalize()
);
// Log any unexpected incoming events for debugging
DuringAny(
When(ProcessEndedEvent)
.Activity(x => x.OfType<GenericOrderEventActivity<ProcessEndedEvent>>())
.Then(ctx => Console.WriteLine($"⚠️ ProcessEndedEvent received in unexpected state {ctx.Saga.CurrentState}"))
);
SetCompletedWhenFinalized();
Log.Information("✅ OrderStateMachine constructor called");
}
}
Event Storming is a fantastic modelling workshop for getting shared understanding of a business domain and designing software to match
Order.API is an independent Bounded Context in the microservices architecture responsible for managing the complete lifecycle of an order. It contains the core business logic for order creation, validation, inventory reservation, discount calculation, and payment initiation.
-
Order acts as the Aggregate Root
-
OrderItem (Child Entity)
-
Contains order items (OrderItems)
-
EnumOrderState → Domain Enum
-
Event Bus Consumers → Domain Event Handler
-
Enforces domain rules such as:
-
Calculating total price
-
Validating order status
-
Managing state transitions (ProcessingInventory → ProcessingPayment → ProcessingEnd → Canceled)
-
-
Implements CQRS
-
Commands: create order, update order state, cancel order
-
Queries: retrieve orders
-
-
Uses Handlers to orchestrate processes
-
Coordinates domain logic with external services
-
Implements Repository pattern in alignment with DDD
-
Uses EF Core for database communication
-
Maps Domain Models ←→ Persistence Models
-
Manages Unit of Work through EF Core context
| Service | DDD Relationship | Description |
|---|---|---|
| Basket | Conformist | Order follows the data model shaped by Basket. |
| Discount | Customer → Supplier | Discount rules are defined by Discount service; Order consumes them. |
| Inventory | Supplier + ACL | Order requests stock count checking. |
| Payment | Supplier | Order initiates the payment workflow. |
| Ocelot Gateway | Open-Host Service | Order is exposed externally via the API Gateway. |
-
Receiving the user's basket and converting it into an order
-
Validating discount information via Discount service
-
Check stock through Inventory service
-
Initiating payment via Payment service
-
Managing the full lifecycle of an order and its domain events
MicroShop.Infra.Sql :
Scaffold-DbContext "Data Source=.;Initial Catalog=MicroShop;Integrated Security=True;TrustServerCertificate=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Context -Force
MicroShop.Infra.Sql :
Scaffold-DbContext "Data Source=.;Initial Catalog=MicroShopLogDB;Integrated Security=True;TrustServerCertificate=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir LogContext -Force
Discount.API :
Scaffold-DbContext "Data Source=.;Initial Catalog=MicroShopDiscount;Integrated Security=True;TrustServerCertificate=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Context -Force
Payment.API :
Scaffold-DbContext "Data Source=.;Initial Catalog=MicroShopPayment;Integrated Security=True;TrustServerCertificate=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Context -Force
MicroShop.OrderApi.Rest :
Add-Migration InitOrderSaga -c OrderStateDbContext
Update-Database -Context OrderStateDbContext
MicroShop.OrderApi.Rest :
Add-Migration InitialOrderEventStoreMigration -c OrderEventStoreDbContext
Update-Database -Context OrderEventStoreDbContext
"spring": {
"application": {
"name": "basket-service"
}
},
"steeltoe": {
"client": {
"consul": {
"discovery": {
"host": "Consul1",
"port": 8500,
"scheme": "http",
"register": true,
"serviceName": "basket-service",
"healthCheckPath": "/health",
"healthCheckInterval": "10s",
"useNetUtils": false
}
}
}
}
builder.Services.AddDiscoveryClient(builder.Configuration);
builder.Services.AddHealthChecks();
// HealthCheck endpoint for Consul
app.MapHealthChecks("/health");
// Register to Consul
app.UseDiscoveryClient();
Server IP, Host Name : 192.168.56.164, Consul1
Client IP, Host Name : 192.168.56.165, Consul2
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install consul
- ⚙️ Consul Server Config :
- 🚑 Set Consul as a Service :
- 🩺🌡️ Service Status :
- ⚙️ Consul Client Config :
- 🚑 Set Consul as a Service :
- 🩺🌡️ Service Status :
📺 Access to the Consul UI from http://Consul1:8500/ui :
dotnet restore
dotnet build
dotnet run
Discount.gRPC ---> Consul1
Basket.API ---> Consul2
OrderApi.Rest ---> Consul1
Inventory.API ---> Consul2
Payment.API ---> Consul1
OcelotAPIGateway ---> Consul2
































