There are many tutorials that go over the features of Azure Service bus and demonstrate how to publish messages but in my experience I haven’t seen many posts that demonstrate the most optimal approach for applications with long lived processes such as a web application.
The typical example I see online looks something like this:
There’s absolutely nothing wrong with the sample code provided by Microsoft however it can be a little miss leading if you don’t consider the context in which this code was written.
The sample application that MS used was a console app which has a very short lifetime. The application will start, create a new client and sender, send a batch of messages and finally dispose the client & sender. In this context the code works without any issues however if the sample code was used in something like a web application you’ll likely start to encounter performance issues. This is because the official recommendation from MS is that both the client and senders should be singletons (docs can be found here: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-performance-improvements?tabs=net-standard-sdk-2#reusing-factories-and-clients).
When using Azure Service Bus in an application that has a long lived process I like to implement the factory pattern to manage the lifetime of the service bus client and sender. You can also manage these instances using dependency injection however that can lead to other issues such as reuse of a closed client.
To demonstrate how this works I’ll do the following:
- Create an interface that will abstract sending messages to a bus
- Create an interface that will abstract the message bus factory
- Implement both interfaces for Azure Service Bus.
- Create a demo application that uses these interfaces.
Start by creating two interfaces. These aren’t completely necessary for managing the lifetime of the service bus connections but they will help decouple our code from the concert Azure Service Bus implementation which is usually a good pattern to following. The two interfaces are:
IMessageBus is an abstraction around publishing messages and can be seen as being equivalent to the class ServiceBusSender from the package Azure.Messaging.ServiceBus. I only included a single method which is responsible for accepting a message and sending to a topic or queue. This interface would be expanded to suit whatever your application needs.
IMessageBusFactory is an abstraction for creating an instances of IMessageBus. Its important to note that these interfaces don’t mention any terms that are specific to Azure Service Bus. This is because they’re intended to be generic interfaces that can be implemented for any messaging infrastructure.
As always there are pros and cons to using abstractions like this so use what makes the most sense for your echo system and application architecture.
- The application is decoupled from concert implementations.
- The app components that publish messages can be unit tested without relying on the actual implementation of Azure Service Bus.
- Developers don’t need to know how Azure Service Bus works to work with this code.
- The interfaces can hide specific details about Azure Service Bus since they should be generic to all messaging frameworks. This means it would be difficult to utilize features that only exist in Azure Service Bus.
The implementation for IMessageBus will contain specific references to the Azure Service Bus SDK and I choose to mark the class as internal as I don’t want to leak this class to the projects that are responsible for publishing messages.
The above class has the responsibility for creating both the ServiceBusClient and ServiceBusSender and ensuring that only one instance is created per connection string and and that the sender is only created once per topic / queue. Additionally the factory will create new instances if the previously created clients are closed.
Dependency Injection Extensions
Since I’ve created an interface and an implementation for the interface its a good idea to create a dependency injection extension method which can be used to register the services in an application that is using this feature.
When using Azure Service Bus in an application that has a long lived process its important to create both the ServiceBusClient and ServiceBusSenders as singletons. Using the factory pattern to manage this complexity could be a good choice and creating interfaces to abstract the function of creating clients and sending messages is also a good idea to ensure your application remains decoupled from concreate implementations and is testable.
You can find the sample application here: https://github.com/WilliamRees/AzureServiceBusDemo