Posted in:

In this series:

So far in this series I've mostly focused on queues which are relatively straightforward to understand. The sender sends a message to the queue, and then listeners can connect to the queue and ask for messages. In this diagram, we send one message to a queue, but there are two listeners (this is called the "competing consumers" pattern). Only one of the two listeners will receive and process the queue message.


Topics and Subscriptions

Azure Service Bus allows you to post messages to a topic rather than a queue. The difference is that a topic doesn't store messages. It simply forwards messages onto any subscriptions on that topic. If a topic has no subscriptions, then posting messages to it essentially results in messages being lost.

Note: this means its really important that you create all necessary subscriptions before posting messages to your topic.

If a topic has two subscriptions, then when we post a message to the topic, a copy of that message is placed on both subscriptions. That means in this example, the single message will be received by listener 3, and by either listener 1 or listener 2 (since they are "competing consumers" on subscription 1).



The type of messaging that topics and subscriptions enable is often called "pub-sub", or "publish and subscribe". It's a great fit when your messages are essentially "events" - in other words, the message simply announces that something has happened.

This allows a single message to result in multiple actions. With this architecture, the sender of the message doesn't usually care what those actions are. It just wants to let other parts of the system that something has happened. We'll talk more about messages as "events" later in this series as this is a very common pattern for event-driven architectures.

Creating a topic and subscription

Earlier in this series we saw how to use the Azure CLI to create an Azure Service Bus namespace and created a queue. Let's also create a topic called topic1 and create two subscriptions for that topic (subscription1 and subscription2):

$topicName = "topic1"
az servicebus topic create -g $resourceGroup `
    --namespace-name $namespaceName -n $topicName

az servicebus topic subscription create -g $resourceGroup `
    --namespace-name $namespaceName `
    --topic-name $topicName -n "subscription1"

az servicebus topic subscription create -g $resourceGroup `
    --namespace-name $namespaceName `
    --topic-name $topicName -n "subscription2"

Posting a Message to a Topic

For this demo, I've updated my Sender application in GitHub to do some basic command line parsing, allowing us to specify not just the Service Bus connection string, and message contents, but to specify either a queue name or a topic name to send to. You can look at the code on GitHub to see how that works.

The more interesting part is that now, instead of using a QueueClient, we use a TopicClient to post the message. Both classes implement implement the ISenderClient interface allowing us to use the same code to send a message whichever option we choose:

ISenderClient senderClient;
if(topicName != null)
    senderClient = new TopicClient(connectionString, topicName);
    senderClient = new QueueClient(connectionString, queueName);

var body = Encoding.UTF8.GetBytes(messageText);
var message = new Message(body);
// configure other properties of the message here if we want
await senderClient.SendAsync(message);
Console.WriteLine($"Sent message to {senderClient.Path}");
await senderClient.CloseAsync();

With these changes, we can run our application with dotnet run and pass our own command line arguments (after -- to ensure they are interpreted as application arguments) like this:

dotnet run -- -c $connectionString -t "topic1" -m "OrderReceived"

If all has gone well, the "OrderReceived" message we sent will have been forwarded by the topic onto both subscription1 and subscription2. Let's see how to receive the messages next.

Receiving Messages on a Subscription

I made a similar change to the Receiver application in the demo project, allowing the user to choose between listening on a queue or a subscription.

To listen on a subscription we need to use a SubscriptionClient instead of a QueueClient, and the SubscriptionClient needs to know both the name of the topic and subscription it should be listening on. But apart from that, everything remains the same, and we can take advantage of the IReceiverClient interface that both clients implement.

IReceiverClient listenerClient;
if (topicName != null)
    listenerClient = new SubscriptionClient(connectionString, topicName, subscriptionName);
    listenerClient = new QueueClient(connectionString, queueName);
var messageHandlerOptions = new MessageHandlerOptions(OnException);
listenerClient.RegisterMessageHandler(OnMessage, messageHandlerOptions);
Console.WriteLine($"Listening on {listenerClient.Path}, press any key");
await listenerClient.CloseAsync();

Now we can run our application and listen to subscription1 on topic1 and we'll get the OrderReceived message we sent earlier.

dotnet run -- -c $connectionString -t "topic1" -s "subscription1"

And if we run again listening on subscription2, we'll see that this subscription also picks up a copy of the OrderReceived message. This essentially allows us to have to separate message handlers, each performing an important task - e.g. one might take payment for the order, and the other might send the customer an order confirmation email.

dotnet run -- -c $connectionString -t "topic1" -s "subscription2"


Topics and subscriptions offer a powerful alternative to queues, allowing us to create more than one handler for the same message. But we haven't said too much yet about why this functionality is so useful. Next up we'll talk about two of the most important types of message - commands and events.


Comment by cookdn

Great series so far. :-)
When you talk about commands and events please make the distinction that these are the abstract application messaging patterns implemented on top of the architectural messaging patterns you have already described.
In so many discussions I have had in relation to system design people conflate the two, talking about infrastructure details such as topics when we should be designing at the the higher level of abstraction.

Comment by Mark Heath

thanks, hoping to find some time soon to write the next instalment and I'll try to make the distinction between these concepts clear

Mark Heath