RO EN

Azure Service Bus (1) — from synchronous HTTP to messages

Azure Service Bus (1) — from synchronous HTTP to messages ✨ Imagine generată cu AI
Doru Bulubașa
21 July 2026
57 views

This is the first of four articles about Azure Service Bus. Here we cover the fundamentals: when to move from synchronous HTTP to messaging and how to choose between Queue and Topic. In the following parts: producers and consumers in .NET, dead-letter queue with retry policies, and Outbox pattern.


The problem with synchronous HTTP calls

The classic scenario: a client places an order. The order service calls the payment service, then the inventory service, then the notification service — all via HTTP, all synchronous, all in the same request.

// Anti-pattern: chain of synchronous calls in the same request
public async Task<IActionResult> PlaceOrder(OrderRequest request)
{
    var order = await _orderRepository.CreateAsync(request);

    await _paymentClient.ChargeAsync(order);        // 1. what if it fails?
    await _inventoryClient.ReserveAsync(order);     // 2. what if it fails after payment?
    await _notificationClient.SendEmailAsync(order); // 3. does the user really need to wait?

    return Ok(order);
}

What's wrong here:

  • Temporal coupling — all three services must be available at the same time. Is the notification service down for 2 minutes? No orders can be placed.
  • Latency adds up — the user waits for the sum of latencies of all calls, including for operations that don't concern them (the email can be sent even after 5 seconds).
  • Partial failures are hard to handle — payment succeeded, but stock reservation failed. Now what? Refund? Retry? In what order?
  • Coupled scaling — a spike in orders hits all services in the chain simultaneously, even if only one is the bottleneck.

What asynchronous messaging changes

With messages, the order service publishes an event (OrderPlaced) and responds immediately. Other services consume the event at their own pace:

// With messaging: publish the event and respond immediately
public async Task<IActionResult> PlaceOrder(OrderRequest request)
{
    var order = await _orderRepository.CreateAsync(request);

    await _messagePublisher.PublishAsync(new OrderPlacedEvent(order));

    return Accepted(order); // 202 -- processing continues asynchronously
}

What you gain:

  • Temporal decoupling — is the notification service down? Messages wait in the queue. When it comes back, it processes them. No orders lost.
  • Minimal latency for the user — you respond after the order is saved, the rest happens in the background.
  • Load leveling — a spike of 1000 orders becomes a queue that consumers process at their own pace, not a simultaneous tsunami across all services.
  • Natural retry — a failed message is automatically retried with configurable retry policies (part 3 of the series).

When NOT to use messages

Balance matters. You stay with synchronous HTTP when:

  • You need immediate response — a card validation, a real-time stock check for display. Request/response is the correct model.
  • The operation is a query — reads are by definition synchronous; messages are for commands and events, not queries.
  • The flow is simple and internal — two services that rarely communicate do not justify messaging infrastructure.

Azure Service Bus — Queue vs. Topic/Subscription

Service Bus offers two delivery models, and choosing between them is the first design decision.

Queue — one producer, one consumer (competing consumers)

The message enters a queue and is processed by exactly one consumer. If you run 5 instances of the consumer, they compete for messages — each message goes to a single instance. The natural model for work distribution:

Producer --> [ Queue: orders ] --> Consumer (instance 1, 2, or 3)

Typical use case: order processing, report generation, any "work item" that must be executed once.

Topic/Subscription — one producer, multiple consumers (pub/sub)

The message is published on a topic, and each subscription receives its own copy. Three services interested in OrderPlaced? Three subscriptions, each with its own copy, processed independently:

                              +--> [ Sub: payments ]      --> Payment service
Producer --> [ Topic ] ---------+--> [ Sub: inventory ]     --> Inventory service
                              +--> [ Sub: notifications ] --> Notification service

Each subscription behaves like its own queue: if the inventory service is down, its messages accumulate in its subscription without affecting payments or notifications.

Filters on subscriptions

A subscription can receive only a subset of messages, via SQL filters on properties:

# Subscription that receives only large orders
az servicebus topic subscription rule create \
  --resource-group my-rg \
  --namespace-name my-servicebus \
  --topic-name orders \
  --subscription-name high-value-orders \
  --name HighValueFilter \
  --filter-sql-expression "TotalAmount > 1000"

How to choose

Criterion Queue Topic/Subscription
How many consume a message Exactly one Each subscription receives a copy
Mental model Work distribution Event broadcasting
Adding a new consumer Competes with others New, independent subscription
Example "Process this order" "An order was placed" (whoever is interested reacts)

Practical rule: if the message is a command ("do X"), use Queue. If it is an event ("Y happened"), use Topic — you don't know today how many services will be interested tomorrow.


Creating resources

# Namespace (Standard tier -- needed for topics)
az servicebus namespace create \
  --name my-servicebus \
  --resource-group my-rg \
  --location westeurope \
  --sku Standard

# Queue
az servicebus queue create \
  --name order-processing \
  --namespace-name my-servicebus \
  --resource-group my-rg

# Topic + subscriptions
az servicebus topic create \
  --name order-events \
  --namespace-name my-servicebus \
  --resource-group my-rg

az servicebus topic subscription create \
  --name inventory \
  --topic-name order-events \
  --namespace-name my-servicebus \
  --resource-group my-rg

Note on tiers: Basic supports only queues. Standard adds topics. Premium brings resource isolation, predictable latency, and messages up to 100 MB — for serious production volume, Premium is worth considering.


What's next

In part two we write code: producers and consumers in .NET with Azure.Messaging.ServiceBus, authentication via Managed Identity, ServiceBusProcessor for robust consumption, and the important settings (prefetch, concurrency, lock duration).

Questions? Write me at contact@ludoprogramming.com.