📬 Part 4 — Sessions, Duplicate Detection & Transactions in Azure Service Bus with .NET

So far, we’ve covered:

  • Part 1: Queues + Worker
  • Part 2: Topics + Subscriptions
  • Part 3: DLQ, Retries & Monitoring

👉 In Part 4, we’ll explore three advanced features:

  1. Sessions → ordered, FIFO processing
  2. Duplicate Detection → prevent double-processing
  3. Transactions → atomic send/receive across operations

🔹 1. Sessions — FIFO Message Processing

By default, Service Bus does not guarantee ordering — messages can arrive out of order if multiple consumers run.

💡 Sessions let you group related messages (conversation, workflow) so they are processed in order by a single consumer.

Example Use Case

  • Order workflow:
    • Step 1 → Payment authorized
    • Step 2 → Inventory updated
    • Step 3 → Invoice generated
  • All steps for the same OrderId must be processed in sequence.

Enabling Sessions

  • When creating a queue or subscription, set RequiresSession = true.
az servicebus queue create -g myRg --namespace-name mysbnamespace -n orders-session-queue --enable-session true

Sending Session Messages

var sender = client.CreateSender("orders-session-queue");

var message = new ServiceBusMessage("Step 1: Payment authorized")
{
    SessionId = "Order-123" // groups messages for this session
};

await sender.SendMessageAsync(message);

👉 All messages with SessionId = "Order-123" are delivered in order.


Receiving Session Messages

var sessionProcessor = client.CreateSessionProcessor("orders-session-queue", new ServiceBusSessionProcessorOptions
{
    MaxConcurrentSessions = 5,   // process multiple sessions in parallel
    AutoCompleteMessages = false
});

sessionProcessor.ProcessMessageAsync += async args =>
{
    Console.WriteLine($"[Session {args.Message.SessionId}] {args.Message.Body}");
    await args.CompleteMessageAsync(args.Message);
};

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

await sessionProcessor.StartProcessingAsync();

💡 Service Bus locks an entire session for one processor at a time → ensuring FIFO ordering within that session.


🔹 2. Duplicate Detection

Sometimes producers accidentally send the same message twice (e.g., network retries). To prevent processing duplicates, enable duplicate detection on queues or topics.


Enabling Duplicate Detection

az servicebus queue create \
  -g myRg --namespace-name mysbnamespace -n orders-dedup-queue \
  --requires-duplicate-detection true \
  --duplicate-detection-history-time-window PT10M
  • Window: PT10M = 10 minutes (ISO 8601 format).
  • Any message with the same MessageId in that time window is ignored.

Example in .NET

var sender = client.CreateSender("orders-dedup-queue");

var message1 = new ServiceBusMessage("Order Created") { MessageId = "Order-123" };
var message2 = new ServiceBusMessage("Order Created") { MessageId = "Order-123" }; // duplicate

await sender.SendMessagesAsync(new[] { message1, message2 });

💡 Only the first message is delivered. The second is silently dropped by Service Bus.


🔹 3. Transactions — Atomic Operations

Sometimes you need multiple Service Bus operations to succeed together or not at all. Example:

  • Receive message from orders-queue
  • Send event to billing-queue
  • Send event to inventory-queue

👉 Either all succeed or none do.


Example: Transaction in .NET

var sender1 = client.CreateSender("billing-queue");
var sender2 = client.CreateSender("inventory-queue");
var receiver = client.CreateReceiver("orders-queue");

ServiceBusReceivedMessage message = await receiver.ReceiveMessageAsync();

using var ts = await client.CreateTransactionAsync();

try
{
    // Complete message within transaction
    await receiver.CompleteMessageAsync(message, ts);

    // Send dependent messages
    await sender1.SendMessageAsync(new ServiceBusMessage("Billing event"), ts);
    await sender2.SendMessageAsync(new ServiceBusMessage("Inventory event"), ts);

    // Commit transaction
    await client.CommitTransactionAsync(ts);
}
catch
{
    await client.RollbackTransactionAsync(ts);
}

💡 Transactions ensure consistency across multiple queues/topics.


📌 Best Practices

  • Sessions:
    • Use when strict ordering is required (e.g., workflows, conversations).
    • Keep sessions short-lived — don’t block others.
  • Duplicate Detection:
    • Always set MessageId (GUID or business key).
    • Pick a history window that balances performance vs duplication risk.
  • Transactions:
    • Use sparingly — they add overhead.
    • Great for financial apps, workflows needing atomic guarantees.

🎯 Conclusion

In this final part, we covered advanced Service Bus features:

  • Sessions for ordered FIFO workflows
  • Duplicate Detection to avoid double-processing
  • Transactions for atomic send/receive

With these tools, you can design resilient, reliable, and consistent .NET microservices that handle real-world complexities like ordering, idempotency, and consistency.


👉 That wraps up our 4-part series on Azure Service Bus + .NET Microservices!

  • Part 1 → Queues + Worker
  • Part 2 → Topics + Subscriptions
  • Part 3 → DLQ + Retries + Monitoring
  • Part 4 → Sessions + Deduplication + Transactions

Comments

Popular posts from this blog

📬 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