🛒 Part 5 — Real-World E-Commerce Microservices with Azure Service Bus & .NET
Over the past 4 parts, we’ve built individual patterns with Azure Service Bus:
- Part 1 → Queues + Worker Services
- Part 2 → Topics + Subscriptions (Pub/Sub)
- Part 3 → DLQ + Retries + Monitoring
- Part 4 → Sessions, Duplicate Detection, Transactions
👉 In this final part, we’ll combine all these concepts into a real-world e-commerce system.
🏗️ Architecture Overview
Imagine a simple e-commerce platform where customers place orders:
Customer → Order API → Service Bus (Topic)
↘ Inventory Service (subscription)
↘ Billing Service (subscription)
↘ Email Service (subscription)
↘ Analytics Service (subscription)
- Order API → accepts orders and publishes events to a Service Bus Topic (
orders-topic). - Inventory Service → updates stock levels (requires ordering → uses Sessions).
- Billing Service → charges customer (requires deduplication).
- Email Service → sends confirmation (fire-and-forget).
- Analytics Service → logs events for BI dashboards.
🔹 Step 1 — Service Bus Setup
- Create namespace
az servicebus namespace create -g myRg -n ecommerce-sb --location eastus
- Create topic
az servicebus topic create -g myRg --namespace-name ecommerce-sb -n orders-topic
- Create subscriptions
az servicebus subscription create -g myRg --namespace-name ecommerce-sb --topic-name orders-topic -n inventory-sub
az servicebus subscription create -g myRg --namespace-name ecommerce-sb --topic-name orders-topic -n billing-sub
az servicebus subscription create -g myRg --namespace-name ecommerce-sb --topic-name orders-topic -n email-sub
az servicebus subscription create -g myRg --namespace-name ecommerce-sb --topic-name orders-topic -n analytics-sub
🔹 Step 2 — Order API (Publisher)
OrderApi/Controllers/OrdersController.cs
using Azure.Messaging.ServiceBus;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly ServiceBusSender _sender;
public OrdersController(ServiceBusClient client, IConfiguration config)
{
_sender = client.CreateSender(config["ServiceBus:TopicName"]);
}
[HttpPost]
public async Task<IActionResult> PlaceOrder([FromBody] Order order)
{
var json = System.Text.Json.JsonSerializer.Serialize(order);
var message = new ServiceBusMessage(json)
{
ContentType = "application/json",
Subject = "OrderPlaced",
MessageId = order.Id.ToString(), // important for deduplication
SessionId = $"Order-{order.Id}" // groups related events
};
await _sender.SendMessageAsync(message);
return Accepted(new { order.Id, Status = "Queued" });
}
}
public record Order(int Id, string Product, int Quantity, decimal Price);
appsettings.json
"ServiceBus": {
"ConnectionString": "<YOUR_SERVICEBUS_CONNECTION_STRING>",
"TopicName": "orders-topic"
}
🔹 Step 3 — Inventory Service (Session Consumer)
Ensures FIFO updates for each order.
var processor = client.CreateSessionProcessor("orders-topic", "inventory-sub", new ServiceBusSessionProcessorOptions
{
MaxConcurrentSessions = 10,
AutoCompleteMessages = false
});
processor.ProcessMessageAsync += async args =>
{
Console.WriteLine($"[Inventory][{args.Message.SessionId}] Updating stock for {args.Message.Body}");
await args.CompleteMessageAsync(args.Message);
};
processor.ProcessErrorAsync += args =>
{
Console.WriteLine($"Inventory error: {args.Exception}");
return Task.CompletedTask;
};
await processor.StartProcessingAsync();
🔹 Step 4 — Billing Service (Deduplication + Transactions)
Avoids double-charging.
var processor = client.CreateProcessor("orders-topic", "billing-sub");
processor.ProcessMessageAsync += async args =>
{
try
{
var order = System.Text.Json.JsonSerializer.Deserialize<Order>(args.Message.Body);
Console.WriteLine($"[Billing] Charging order {order!.Id}...");
// Idempotency check: MessageId = OrderId
// Ensure your DB/Redis checks if already processed
bool alreadyProcessed = await BillingDb.IsProcessedAsync(order.Id);
if (alreadyProcessed)
{
Console.WriteLine($"[Billing] Order {order.Id} already charged.");
await args.CompleteMessageAsync(args.Message);
return;
}
// Transaction: complete message + record billing
using var ts = await client.CreateTransactionAsync();
await args.CompleteMessageAsync(args.Message, ts);
await BillingDb.MarkProcessedAsync(order.Id, ts);
await client.CommitTransactionAsync(ts);
}
catch (Exception ex)
{
Console.WriteLine($"Billing failed: {ex.Message}");
await args.AbandonMessageAsync(args.Message);
}
};
🔹 Step 5 — Email Service (Simple Consumer)
Fire-and-forget confirmation.
var processor = client.CreateProcessor("orders-topic", "email-sub");
processor.ProcessMessageAsync += async args =>
{
Console.WriteLine($"[Email] Sending confirmation for: {args.Message.Body}");
await args.CompleteMessageAsync(args.Message);
};
🔹 Step 6 — Analytics Service (Monitoring Consumer)
Captures events for BI dashboards.
var processor = client.CreateProcessor("orders-topic", "analytics-sub");
processor.ProcessMessageAsync += async args =>
{
Console.WriteLine($"[Analytics] Logging event: {args.Message.Body}");
// Optionally forward to Cosmos DB or Data Lake
await args.CompleteMessageAsync(args.Message);
};
🔹 Step 7 — Dead Letter Queue (DLQ) Monitor
Each subscription (inventory-sub/$DeadLetterQueue, etc.) has a DLQ.
Example DLQ processor:
var receiver = client.CreateReceiver("orders-topic", "inventory-sub", new ServiceBusReceiverOptions
{
SubQueue = SubQueue.DeadLetter
});
var messages = await receiver.ReceiveMessagesAsync(10);
foreach (var msg in messages)
{
Console.WriteLine($"[DLQ][Inventory] Id={msg.MessageId}, Reason={msg.DeadLetterReason}");
await receiver.CompleteMessageAsync(msg);
}
🔹 Step 8 — Monitoring with App Insights
Enable in each service:
builder.Services.AddApplicationInsightsTelemetry();
Track custom telemetry:
telemetry.TrackEvent("OrderProcessed", new Dictionary<string, string>
{
{ "OrderId", order.Id.ToString() },
{ "Service", "Billing" }
});
KQL Query example:
customEvents
| where name == "OrderProcessed"
| summarize Count = count() by customDimensions.Service
📌 End-to-End Flow
- Customer places order → API publishes to
orders-topic. - Inventory Service → updates stock (session ensures ordering).
- Billing Service → charges (deduplication ensures idempotency).
- Email Service → sends confirmation.
- Analytics Service → logs for BI dashboards.
- DLQ Processor → monitors failures.
- App Insights → gives visibility across services.
✅ Best Practices
- Use Sessions only where strict ordering is required.
- Always set
MessageIdfor deduplication & idempotency. - Use Transactions for workflows spanning multiple operations.
- Monitor DLQs and set up alerts.
- Add Application Insights everywhere for observability.
- Secure services with Managed Identity + RBAC instead of connection strings.
🎯 Conclusion
With Azure Service Bus, we’ve built a real-world e-commerce microservice system that is:
- Decoupled → services don’t depend on each other
- Resilient → retries, DLQs, idempotency
- Scalable → independent consumers scale as needed
- Observable → full monitoring with App Insights
Comments
Post a Comment