June 30th, 2022

Using Azure Service Bus in the wild

This post was written by guest blogger Paul Michaels.

When you decide to adopt a message broker, there are a relatively small number of reasons:

  • You want to use the queueing functionality to act as a shock absorber for spikes in traffic.
  • You’re trying to ingest data into a data warehouse.
  • You want a loosely coupled communication between two or more distinct systems.
  • Situations where you need to process a backlog of data.

It’s worth noting that introducing a message broker into your architecture isn’t without cost. There’s a financial cost if you choose a cloud provider. And if you don’t, you’ll need to manage the infrastructure that the cloud provider supplies, which may be a higher cost. However, there’s also a complexity and performance cost. It’s far faster and simpler to directly call an API than to put a message on a queue and have that service pick up the message; however, using a message broker does avoid the coupling that you get when you use a direct API call.

After deciding to use a message broker, whether Azure Service Bus or an alternative, you suddenly have many considerations. This post focuses on Azure Service Bus, and we’ll discuss some of those considerations. Specifically, we’ll discuss how you may architect your solution, the setup process, security considerations, and dealing with failure.

Architecture

The most important initial consideration you have is how to organize your infrastructure.

It may be that what you need isn’t Azure Service Bus, but Azure Event Hubs or even Azure Storage Queues. Again, there isn’t a single answer here. If you’re sending information about something that has happened, rather than just that it has happened, you want a messaging solution. Storage Queues cost less but provide fewer features. We’ll focus on Azure Service Bus here. If your use case only requires a simple queue without ordering, or the other features that Service Bus provides, a Storage Queue may be the best answer.

Your architecture depends largely on your use case. But as we’ve discussed above, those use cases (whilst nuanced), probably fall into a few categories. Your specific situation may cover more than one of these use cases. Let’s move onto your first decision: queues or topics.

Queues

This decision isn’t an “either-or”. You can use both within the same Service Bus instance. If you’ve never used a queue before, consider it similar to a physical bus queue. All things being equal, the first person in a bus queue is the first person to board the bus. As you join a bus queue, the bus may not be there yet. In fact, the bus may never arrive. The same is true for queues: messages can be pushed to a queue and sit there with no consumer. However, once the message is consumed, it’s removed from the queue. While not always necessarily true, but it’s probably true enough for this discussion.

Queues work well in situations where multiple consumers need to work from the same backlog of work. They work well as shock absorbers. For example, if you suddenly receive a million orders in a few minutes. However, it’s difficult (although not impossible) to use them for communication between multiple systems.

Topics

Topics (or pub/sub), in some ways, can be thought of as private queues. That is, you subscribe to a topic and are provided a private queue. However, unlike a queue, a message is only delivered where there’s at least a single subscription to a topic, and only to the subscribers.

Topics work best in cases where you wish to communicate between systems. For example, where your architecture resembles the following distributed system:

Service Bus topics

Deployment

You can deploy an Azure Service Bus by navigating to the Azure portal, selecting that you want a new Service Bus instance, and selecting Deploy. This workflow is sufficient if you’re deploying an instance to play around with. If your estate is of any kind of size or complexity, consider Infrastructure as Code (IaC). The following IaC options are currently available for Azure:

  • Azure Bicep from Microsoft is a recent emergence into this space. It has a Terraform feel to it, so if you’re familiar with Terraform it might be a good option. Bicep does provide one distinct advantage over the other two: new Azure features are immediately available. The obvious downside being that it’s Azure only. It’s also open source.
  • Terraform from HashiCorp. Terraform has been around (relatively speaking, circa 2014) for a while, and is open source. They have a wide coverage of most cloud providers, including Azure. However, for brand new features, you’ll likely have to wait for some time. The documentation to get started is comprehensive, and once you get used to the syntax, easy to use. It’s free for most use cases, including commercial use. You can pay for extra things, like support.
  • Pulumi. Pulumi is an open-source tool that, in many ways, is similar to Terraform. It covers the major cloud providers, although it’s younger than Terraform, so covers fewer overall. The advantage to Pulumi is that, unlike the other options, you can write your infrastructure using an actual programming language instead of JSON.

My advice is that you should probably pick one, and stick with it. Multiple tools trying to manage your infrastructure might cause some serious headaches.

Security and client

There are SDKs for other languages, but assuming that you’re using .NET to connect to the Service Bus instance, Azure.Messaging.ServiceBus is the most recent library, and the one that Microsoft recommends using. This library also gives you the option to connect using a Managed Identity, rather than a connection string.

Using a Managed Identity has many advantages. Some advantages include:

  • You no longer need to store your connection string.
  • You have granular control over the client app’s permissions within Service Bus.

Message format

Azure Service Bus allows you to send any message you choose. The following code sample shows how to send a message:

var sender = _serviceBusClient.CreateSender(queueName);                        
var message = new ServiceBusMessage(text);
await sender.SendMessageAsync(message);
await sender.CloseAsync();

Take some time to consider the appropriate messaging schema in your app. For example, you could send the following message to indicate that a sales order had been raised:

var message = new ServiceBusMessage("sales order raised: 123456");

If you’re only using a message broker to notify of sales orders, the preceding code sample will suffice. For most real-world use cases, the code sample is extreme.

Let’s first talk about format. It may be beneficial to decide on a defined format. As with any exchange of information, both sides need to understand the information being passed. A typical solution is to use an exchange format such as XML or JSON. Your message may now look like this JSON:

{
    "SalesOrder": "123456"
}

At this point, we need to consider what a distributed system actually means. Let’s imagine that you have two services: A and B.

Service Bus services

It’s probable that these two services will have independent code bases, deployment pipelines, and different teams assigned to their development and maintenance. After all, that’s one of the advantages of a distributed system. If both of these services understand the JSON above, they can both work flawlessly. Service A might raise a sales order, and Service B might send out an order confirmation.

Note that, for simplicity, I’m only including a single attribute of the sales order (i.e. the number); however, in a real life scenario, a sales order would likely have dozens – if not hundreds – of attributes: order value, line items, customer details, and so forth. Having said that, the same principle outlined here still applies.

Let’s now imagine that we decide to add a piece of information: the date.

{
    "SalesOrder": "123456",
    "Date": "2022-05-19"
}

If we add the date, we can probably expect Service B to cope with that extra data. It may not do anything with the date, but the code should continue to run.

What if we now wish to make a new change? For example:

{
    "SalesOrderNumber": "123456",
    "Date": "2022-05-19"
}

This change is likely to break Service B. No matter how defensively the team wrote the service, it’s expecting SalesOrder and is getting SalesOrderNumber.

One way around such an issue is to add a message version. There are various ways to version, but something like this approach would work:

{
    "Version": "1",
    "SalesOrderNumber": "123456",
    "Date": "2022-05-19"
}

That way, consuming code can make assumptions about what the message looks like based on the version. Finally, you may wish to organize the message in such a way as, were you to want a different message, you could identify the format of each. Imagine that there’s now a requirement for a sales order canceled message. You could use a separate queue, but the following type of syntax allows you to reuse much of the same writing and parsing code:

{
    "MessageInfo": {
        "Version": "1",
        "Type": "SalesOrder"
    },
    "Message": {
        "Version": "1",
        "SalesOrderNumber": "123456",
        "Date": "2022-05-19"
    }
}

Request and response

In a large messaging system, you’ll end up needing some form of correlation between multiple messages. Imagine that the user places an order, and the action is to email a confirmation. You’ll want to associate the messages that signal:

  • The user has placed an order.
  • An email should be sent.

The order number would likely be a good correlation. If you don’t have a consistent piece of information like order number, you may need to create one.

In Azure Service Bus, a correlation ID is provided for you. For example:

var sender = _serviceBusClient.CreateSender(queueName);                        
var message = new ServiceBusMessage(text);
message.CorrelationId = Guid.NewGuid();

Failure

The last topic is dealing with failures. This topic is probably the most overlooked when dealing with a message broker. Most, if not all, message brokers provide you with the equivalent of a Dead Letter Queue (DLQ). The DLQ is a place to put messages that can’t be processed, in order to free the rest of the queue. For Service Bus, there’s a finite number of reasons that a message can end up in the queue. Broadly, it means the message cannot (too many retries), has not (timeout), or will not (too many redirects) be processed.

This behavior is provided out-of-the-box with Azure Service Bus. The issue you have is what to do with the messages. A common pattern is to train one of the support or engineering teams on the potential failure reasons for your system. They would then be given sufficient privileges in the portal, or provided with a tool such as Service Bus Explorer – which enables features such as changing and resubmitting a message. However, you do lose an element of tracing.

You can create a background process. For example, an Azure Function to read the queue. This approach allows for some of the more obvious issues to be addressed automatically. You may find it best to start with Service Bus Explorer. As you start to scale up, you can identify repeatable issues that can be dealt with automatically.

Summary

In this article, we’ve spoken about how you can design a Service Bus architecture for your use case. We’ve covered when you may wish to use queues and when topics are more appropriate. We then discussed deployment considerations and the options around IaC. We talked about how you might connect to Azure Service Bus with a .NET client library. Finally, we discussed what you can do about a failure.

Category
Azure SDK
Topics
sdk

Author

Interested in all things programming and tech. I frequently blog about the MS stack here: https://pmichaels.net

3 comments

Discussion is closed. Login to edit/delete existing comments.

  • Tom Schlarman · Edited

    Quote:
    Note that, for simplicity, I’m only including a single attribute of the sales order (i.e. the number); however, in a real life scenario, a sales order would likely have dozens – if not hundreds – of attributes: order value, line items, customer details, and so forth.

    Good article, but you should probably have mentioned there is a max message size limit and sending larger messages will slow things down.

    • LaDonna QuinnMicrosoft employee

      Thanks Tom. Since Paul is a first-time guest blogger for the Azure SDK blog site, the comment notification may be in his spam email. I will let him know your feedback and see if he would like to make any updates to the blog post. Again, thank you for reading the blog and providing useful feedback.