Service Bus Queue vs. Topic
The core difference is the delivery model: a queue delivers each message to exactly one consumer, while a topic delivers a copy of each message to every interested subscriber.
Queue: point-to-point
Senders put messages on the queue, and receivers pull from it. If several receivers listen to the same queue, they are competing consumers: each message goes to only one of them, so adding receivers spreads the load.
Sender(s) ──► [ Queue ] ──► Receiver A (or B, or C; one gets each message)
Topic: publish/subscribe
Senders publish to a topic, but receivers don't read from the topic itself. They read from subscriptions, each of which acts like its own virtual queue. Every subscription receives its own copy of each message that matches its filter.
┌─► [ Subscription: billing ] ──► Billing service
Sender ──► [ Topic ] ─┼─► [ Subscription: shipping ] ──► Shipping service
└─► [ Subscription: analytics ] ──► Analytics service
Within a single subscription, multiple consumers still compete, so you can scale out each subscriber independently.
Side-by-side
| Queue | Topic + subscriptions | |
|---|---|---|
| Pattern | Point-to-point | Publish/subscribe (one-to-many) |
| Copies per message | One, consumed once | One per matching subscription |
| Receivers read from | The queue | A subscription (not the topic) |
| Sender knows the receivers? | Effectively one logical consumer | No, the sender is unaware of subscribers |
| Filtering | None | Rules and filters per subscription (SQL or correlation filters, with optional actions) |
| Adding a new consumer | Competes with existing ones for messages | Create a new subscription; existing ones are unaffected |
| Dead-letter queue | One DLQ | One DLQ per subscription |
| Tiers | Standard and Premium | Standard and Premium |
Filters on subscriptions
Topics get much of their power from subscription rules:
// Only receive orders from the EU
await adminClient.CreateSubscriptionAsync(
new CreateSubscriptionOptions("orders", "eu-orders"),
new CreateRuleOptions("EuOnly", new SqlRuleFilter("region = 'EU'")));
A subscription with no rule gets everything by default. Note that a subscription's default rule is "match all", so if you add your own filter, remove the default rule or it will still match every message.
Sending and receiving in code
The code is nearly identical. The sender doesn't care which it targets, and only the receiver changes:
// Send (queue name or topic name)
var sender = client.CreateSender("orders");
await sender.SendMessageAsync(new ServiceBusMessage("Order #1234 placed"));
// Receive from a queue
var queueReceiver = client.CreateReceiver("orders");
// Receive from a topic subscription
var subReceiver = client.CreateReceiver("orders", "billing");
Because sending code is the same, you can start with a queue and move to a topic later without changing producers (you'd recreate the entity, though, as a queue can't be converted into a topic).
When to use which
Use a queue when:
- One service should process each message (work distribution, task processing).
- You want load leveling or competing consumers for throughput.
- The sender and receiver are tightly paired, e.g. "process this payment."
Use a topic when:
- Multiple independent systems must react to the same event (an "order placed" event consumed by billing, shipping, and notifications).
- Different consumers want different subsets of messages (via filters).
- You expect to add new consumers over time without touching the publisher.
Shared behavior
Queues and subscriptions share the same features: peek-lock, dead-lettering, sessions, scheduled messages, duplicate detection (configured at the topic level for topics), and TTL. A subscription is, in effect, a queue fed by a topic.
Rule of thumb
- Command ("do this work") → queue
- Event ("this happened") → topic
If you have a single consumer today but may add more, a topic with one subscription is a cheap way to keep your options open.
No comments:
Post a Comment
Note: only a member of this blog may post a comment.