๐Ÿ“ฌ Part 2 — Azure Service Bus Topics & Subscriptions with .NET Microservices

In part 1 we built a Queue-based system:

  • API → sends orders to a queue
  • Worker → consumes and processes them

That works for point-to-point communication. But what if multiple services need to react to the same event? For example:

  • Inventory Service → update stock
  • Email Service → send confirmation email
  • Billing Service → charge the customer

๐Ÿ‘‰ That’s where Service Bus Topics & Subscriptions shine!


๐ŸŒŸ Topics & Subscriptions: What They Are

  • Topic → like a “broadcast station” where messages are published.
  • Subscription → independent queue that receives a copy of each message.
  • A single message → can fan out to many subscribers.

๐Ÿ’ก Each subscription can also apply filters to receive only certain messages.


๐Ÿ—️ Architecture

OrderApi (Producer) → OrdersTopic
                                ↘ InventorySubscription → InventoryWorker
                                ↘ EmailSubscription     → EmailWorker
                                ↘ BillingSubscription   → BillingWorker
  • OrderApi publishes messages into orders-topic.
  • Each worker microservice listens on its own subscription.
  • Subscriptions are independent → one failing worker doesn’t affect others.

⚙️ Step 1 — Create Topic & Subscriptions

You can do this in the Azure Portal or with CLI:

# Create a topic
az servicebus topic create -g myRg --namespace-name mysbnamespace -n orders-topic

# Create subscriptions
az servicebus subscription create -g myRg --namespace-name mysbnamespace \
  --topic-name orders-topic -n inventory-subscription

az servicebus subscription create -g myRg --namespace-name mysbnamespace \
  --topic-name orders-topic -n email-subscription

az servicebus subscription create -g myRg --namespace-name mysbnamespace \
  --topic-name orders-topic -n billing-subscription

๐Ÿ“ค Step 2 — Producer (OrderApi) sends to Topic

Update OrderApi to publish messages to a topic instead of a queue.

using Azure.Messaging.ServiceBus;

public class ServiceBusSenderService : IAsyncDisposable
{
    private readonly ServiceBusClient _client;
    private readonly ServiceBusSender _sender;

    public ServiceBusSenderService(IConfiguration config)
    {
        _client = new ServiceBusClient(config["ServiceBus:ConnectionString"]);
        _sender = _client.CreateSender(config["ServiceBus:TopicName"]); // topic instead of queue
    }

    public async Task SendOrderMessageAsync(Order order)
    {
        var json = System.Text.Json.JsonSerializer.Serialize(order);
        var message = new ServiceBusMessage(json)
        {
            ContentType = "application/json",
            Subject = "OrderPlaced",
            MessageId = Guid.NewGuid().ToString()
        };
        await _sender.SendMessageAsync(message);
    }

    public async ValueTask DisposeAsync()
    {
        await _sender.DisposeAsync();
        await _client.DisposeAsync();
    }
}

appsettings.json

"ServiceBus": {
  "ConnectionString": "<YOUR_SERVICEBUS_CONNECTION_STRING>",
  "TopicName": "orders-topic"
}

๐Ÿ“ฅ Step 3 — Consumers (Subscribers)

Each microservice connects to the same topic but listens to its own subscription.

Inventory Worker

var processor = client.CreateProcessor("orders-topic", "inventory-subscription");

processor.ProcessMessageAsync += async args =>
{
    var body = args.Message.Body.ToString();
    Console.WriteLine($"[Inventory] Updating stock for: {body}");
    await args.CompleteMessageAsync(args.Message);
};

processor.ProcessErrorAsync += args =>
{
    Console.WriteLine($"Inventory error: {args.Exception}");
    return Task.CompletedTask;
};

await processor.StartProcessingAsync();

Email Worker

var processor = client.CreateProcessor("orders-topic", "email-subscription");

processor.ProcessMessageAsync += async args =>
{
    var body = args.Message.Body.ToString();
    Console.WriteLine($"[Email] Sending confirmation: {body}");
    await args.CompleteMessageAsync(args.Message);
};

await processor.StartProcessingAsync();

Billing Worker

var processor = client.CreateProcessor("orders-topic", "billing-subscription");

processor.ProcessMessageAsync += async args =>
{
    var body = args.Message.Body.ToString();
    Console.WriteLine($"[Billing] Charging payment for: {body}");
    await args.CompleteMessageAsync(args.Message);
};

await processor.StartProcessingAsync();

๐ŸŽ›️ Step 4 — Subscription Filters

You can configure subscriptions to only receive specific messages.

Example: Only route messages with Product = "Laptop" to premium-subscription.

az servicebus subscription rule create \
  -g myRg --namespace-name mysbnamespace --topic-name orders-topic \
  --subscription-name premium-subscription -n LaptopOnlyRule \
  --filter-sql-expression "Product = 'Laptop'"

Now, premium-subscription only gets Laptop orders, ignoring everything else.


๐Ÿงช Step 5 — Test It

  1. Run OrderApi and POST an order:
curl -X POST http://localhost:5000/api/orders \
  -H "Content-Type: application/json" \
  -d '{"id":1,"product":"Laptop","quantity":1,"price":999.99}'
  1. Logs:
[Inventory] Updating stock for: {"Id":1,"Product":"Laptop",...}
[Email] Sending confirmation: {"Id":1,"Product":"Laptop",...}
[Billing] Charging payment for: {"Id":1,"Product":"Laptop",...}
  1. If you send "Mouse" instead, the premium-subscription will not receive it (due to filter).

๐Ÿ“Œ Use Cases for Topics

  • Event-driven systems: One event → many subscribers.
  • Fan-out architecture: Notify multiple services without tight coupling.
  • Selective routing: Use filters to send only relevant events to subscribers.
  • Auditing: Create a subscription just for logging/monitoring purposes.

๐Ÿ“Œ Best Practices

  • ✅ Use filters to avoid unnecessary message processing.
  • ✅ Always handle DLQs for each subscription.
  • ✅ Scale consumers independently — one slow consumer won’t affect others.
  • ✅ Make consumers idempotent (handle duplicate messages).
  • ✅ Secure with Managed Identity + RBAC instead of connection strings in production.

๐ŸŽฏ Conclusion

In this part, we extended our microservices system with Topics & Subscriptions:

  • One producer publishes to a topic
  • Multiple consumers process the same message independently
  • Filters help with selective message routing

This pattern is the backbone of event-driven microservices on Azure.


๐Ÿ‘‰ In Part 3, we’ll dive into:

  • Monitoring with Application Insights
  • Dead Letter Queue (DLQ) handling
  • Retry strategies and exponential backoff

Comments

Popular posts from this blog

๐Ÿ“ฌ Part 4 — Sessions, Duplicate Detection & Transactions in Azure Service Bus with .NET

๐Ÿ“ฌ Part 3 — Dead Letter Queue, Retries, and Monitoring in Azure Service Bus with .NET

๐Ÿ›’ Part 5 — Real-World E-Commerce Microservices with Azure Service Bus & .NET