Domain 3 β€” Module 2 of 6 33%
18 of 27 overall
Domain 3: Connect to and consume Azure services Free ⏱ ~11 min read

Service Bus Topics + Subscriptions

One-to-many messaging with filters. Topics, subscriptions, SQL and correlation filter rules, and the patterns that fan out a single AI event to multiple downstream workers.

What a topic adds over a queue

Simple explanation

A queue has one logical reader; a topic can have many. When a sender publishes a message to a topic, every subscription on that topic gets its own copy. Each subscription has its own receiver, its own delivery count, its own DLQ.

And subscriptions can have filters. The β€œembed” subscription sees only messages where kind = 'article-created'; the β€œaudit” subscription sees everything. Filters happen in Service Bus β€” the wrong subscriptions never see irrelevant messages.

For AI: when a single event (β€œuser uploaded a document”) needs to fan out to multiple workers (embed it, scan it for PII, kick off translation), a topic with subscriptions is exactly right.

The fan-out model

                       β”Œβ”€β”€ Subscription "embed"      β†’ Embedder worker
Topic: doc-events  ────┼── Subscription "scan-pii"   β†’ PII scanner worker
                       β”œβ”€β”€ Subscription "translate"  β†’ Translation worker
                       └── Subscription "audit"      β†’ Audit pipeline (no filter)

When you publish { kind: "article-created", id: "art-42", lang: "en" }, all four subscriptions get a copy. Filters can drop the copy at subscription time so workers only see what they care about.

Filter rules

Three filter types. Use correlation when you can β€” it's the fastest. Fall back to SQL when you need richer logic.
FeatureBoolean filter (TrueFilter / FalseFilter)SQL filterCorrelation filter
SyntaxNo expression β€” true or falseSQL-92-like expression on propertiesEquality on system + user properties
PowerAll or nothingMost expressiveLimited but very efficient
PerformanceFree (no eval)Slightly higher CPU per matchOptimised β€” broker indexes correlation properties
Best forAudit subscription that takes everything, or a sub that's effectively disabledFiltering by message body / multiple properties / rangesCommon single-property equality (event type, tenant)
# SQL filter β€” only messages where kind = 'article-created' AND lang IN ('en','fr')
az servicebus topic subscription rule create \
  --resource-group roo-prod --namespace-name roo-sb \
  --topic-name doc-events --subscription-name embed \
  --name embed-filter \
  --filter-sql-expression "kind = 'article-created' AND lang IN ('en','fr')"

# Correlation filter β€” the fast path for single-property equality
az servicebus topic subscription rule create \
  --resource-group roo-prod --namespace-name roo-sb \
  --topic-name doc-events --subscription-name scan-pii \
  --name pii-filter \
  --correlation-filter '{"properties":{"kind":"article-created"}}'

When you create a subscription, the default rule is 1=1 (true filter, takes everything). To filter, you remove that default rule and add your own.

# Remove the default $Default rule
az servicebus topic subscription rule delete \
  --resource-group roo-prod --namespace-name roo-sb \
  --topic-name doc-events --subscription-name embed \
  --name '$Default'

Senders set properties; subscribers read them

# Sender β€” set custom properties on the message
message = ServiceBusMessage(
    body=json.dumps(payload),
    application_properties={
        "kind": "article-created",
        "lang": "en",
        "tenant": "tidewater",
    },
)
await sender.send_messages(message)

These properties are what filters evaluate. The body itself is opaque to filters β€” filters only see envelope properties.

Property typeWhat it isFilter access
Application propertiesArbitrary key/value set by the senderkind, lang β€” referenced as bare names in SQL filters
System propertiesSet by Service Bus (MessageId, CorrelationId, ContentType, To, …)Referenced as sys.<name>
Message bodyThe actual payloadNOT visible to filters
Real-world example: Theo's compliance routing

Tidewater Health publishes one event per patient-record edit to a topic. Three subscriptions consume it differently:

  1. embed β€” kind = 'record-edited' AND fhir_resource = 'Encounter' (only certain resource types feed RAG)
  2. audit β€” no filter (every change must be logged)
  3. alert β€” kind = 'record-edited' AND severity = 'critical' (paged alerts only for critical edits)

Three workers, three workloads, one source of truth. If Theo adds a fourth concern next quarter (e.g., dispatch to a pharmacy AI), it’s another subscription with its filter β€” no change to the publisher.

Forwarding β€” chain subscriptions

A subscription can auto-forward its messages to another queue or topic. Useful for:

  • Fanning a topic into a queue your worker already consumes
  • Routing high-volume subscriptions into Premium-tier targets for performance
  • Splitting a single subscription across multiple downstream workers
az servicebus topic subscription create \
  --resource-group roo-prod --namespace-name roo-sb \
  --topic-name doc-events --name embed \
  --forward-to embed-jobs   # forwarding to a queue named 'embed-jobs'

Now embed’s messages land in the embed-jobs queue automatically, and your existing queue worker consumes them.

Sessions on topics

Subscriptions support the same session model as queues. If a topic is configured for sessions, all subscriptions inherit that requirement, and senders must set session_id on every message.

This is the right pattern when each subscriber needs FIFO within a session AND multiple subscribers care about the same events.

Topic vs queue vs Event Grid β€” quick decision

PatternUse
QueueOne sender, one (logical) consumer, durable
Topic + subscriptionsOne publisher, many consumers, broker-side filtering, durable
Event GridMany publishers, many subscribers, push delivery, schema standardised, retries

Service Bus is pull-based and durable; Event Grid is push-based and event-routing-focused. We cover Event Grid in the next module.

Key terms

Question

What's the difference between a Service Bus queue and a topic?

Click or press Enter to reveal answer

Answer

A queue has one logical receiver β€” every message is delivered exactly once (per delivery attempt). A topic has many subscriptions β€” each subscription gets its own copy of every message (subject to its filter), with its own receiver, delivery count, and DLQ.

Click to flip back

Question

What's the difference between a SQL filter and a correlation filter?

Click or press Enter to reveal answer

Answer

SQL filter β€” full expression syntax over properties (`kind = 'X' AND lang IN ('en','fr')`). Most expressive but slightly higher CPU per match. Correlation filter β€” equality only on system or user properties β€” much faster (broker indexes correlation properties). Use correlation when you can.

Click to flip back

Question

Can a Service Bus filter inspect the message body?

Click or press Enter to reveal answer

Answer

No. Filters only see envelope properties (system + user). The body is opaque. If you need to route on body content, set the relevant fields as application properties when sending β€” the same data, but addressable by filters.

Click to flip back

Question

What is subscription forwarding?

Click or press Enter to reveal answer

Answer

A subscription can be configured to auto-forward its messages to another queue or topic. Lets you split workloads, chain pipelines, or fan a topic into a queue that an existing worker already consumes β€” without changing the worker's code.

Click to flip back

Question

What is the `$Default` rule on a new Service Bus subscription?

Click or press Enter to reveal answer

Answer

A built-in TrueFilter that matches every message. New subscriptions are created with this rule so they receive everything by default. To filter, remove `$Default` and add your own SQL or correlation filter rule.

Click to flip back

Knowledge check

Knowledge Check

Mira publishes one message per uploaded image to a topic. Three teams want to consume: an embedder (only images), a moderation team (only adult content flagged at upload), and an audit team (everything). Which structure fits best?

Knowledge Check

Theo wants the audit subscription to receive every message regardless of body content. The other two subscriptions filter on a `severity` property. What should the audit subscription do?

Knowledge Check

Lin's filter `kind = 'event' AND tenant = 'acme'` runs on a topic with millions of messages per day. Performance is poor. Which filter optimisation is the simplest?