Implementing the Request-Response Pattern with Azure Service Bus: A Practical Guide for Reliable Communication in Distributed Systems

Learn how to use Azure Service Bus for asynchronous request-response messaging, including topics and filters for even more efficient, scalable communication in your microservices architecture.

Md hasanuzzzaman
5 min readNov 7, 2024
Azure Service Bus with topic and filter

In modern distributed systems, Azure Service Bus provides a robust way to implement request-response messaging patterns between services. As an enterprise-grade message broker, Azure Service Bus offers queues for direct point-to-point messaging, as well as topics with subscriptions for publish-subscribe messaging. Using topics and message filters in a request-response pattern allows for multi-consumer support and more targeted message delivery.

This guide covers setting up Azure Service Bus queues and topics with filters, implementing request-response messaging, and best practices for reliability.

What is a request-response pattern?

In the request-response pattern, a service (the requester) sends a message expecting a response. With Azure Service Bus, this typically involves:

  • A request queue for sending requests and response queue for receiving responses in point-to-point communication.
  • Topics and filters to direct messages to multiple subscribers based on criteria, useful when multiple consumers need specific responses or notifications.

Using Azure Service Bus topics and filters, you could achieve flexible request-response messaging while maintaining efficiency in multi-consumer environments.

Why Use Azure Service Bus Topics with Filters?

Azure Service Bus topics provide a publish-subscribe model, allowing multiple consumers to listen to the same topic through individual subscriptions. The use of filters allows consumers to determine which messages interest them based on attributes, allowing them to send messages more selectively. This is especially useful for:

  • Multi-tenant applications, where different services or customers only want specific message types.
  • Workflow orchestration, where various services process different parts of a request.

Topics and filters allow for more precise control of message delivery, improving performance and reducing implementation costs.

Step 1: Setting Up Azure Service Bus Topics and Subscriptions

To implement a request-response pattern with topics, set up:

  1. An Azure Service Bus Topic for broadcasting messages to various subscribers.
  2. Subscriptions with filters to control which messages each subscriber receives.

In the Azure portal:

  1. Navigate to your Service Bus namespace and create a topic (e.g., request-response-topic).
  2. Create subscriptions (e.g., ResponseA, ResponseB) with different filters based on message properties, like CorrelationId or ResponseType.

Step 2: Sending Requests with Topics

In this approach, each request message includes properties like CorrelationId to uniquely identify requests. Additionally, specify properties that will allow subscriptions to filter messages.

Here’s how to send a message to a topic in C#:

public async Task SendRequestAsync(string requestData, string responseType)
{
var client = new TopicClient(connectionString, topicName);
var message = new Message(Encoding.UTF8.GetBytes(requestData))
{
CorrelationId = Guid.NewGuid().ToString(),
Label = responseType
};

// Additional message properties can be set for filtering.
message.UserProperties["ResponseType"] = responseType;

await client.SendAsync(message);
await client.CloseAsync();
}
  • Label: An optional property for quick identification of message types.
  • UserProperties: Custom properties like ResponseType that can be used for filtering.

Step 3: Configuring Filters on Subscriptions

Filters allow subscribers to selectively receive messages based on message properties. Azure Service Bus provides various types of filters, including SQL filters and Correlation filters.

For example, if ResponseA subscription should only receive messages with ResponseType = “A”:

var responseSubscriptionClient = new SubscriptionClient(connectionString, topicName, "ResponseA");
var sqlFilter = new SqlFilter("ResponseType = 'A'");
await responseSubscriptionClient.AddRuleAsync(new RuleDescription("ResponseFilter", sqlFilter));

This ensures only messages where ResponseType is “A” are delivered to the ResponseA subscription.

Step 4: Processing the Request and Sending a Response Back to the Topic

Each responder listens to its respective subscription, processes the message, and sends the response back to the response subscription on the original topic, using the ReplyTo property. This step completes the request-response cycle by ensuring the original requester receives the processed response.

Example code for listening on the response subscription:

public async Task ListenForRequestsAndRespondAsync()
{
var subscriptionClient = new SubscriptionClient(connectionString, topicName, "ResponseA");

subscriptionClient.RegisterMessageHandler(async (message, token) =>
{
// Extract and process the request data
var requestData = Encoding.UTF8.GetString(message.Body);
var responseType = message.UserProperties["ResponseType"] as string;

// Process the request...

// Prepare the response message
var responseMessage = new Message(Encoding.UTF8.GetBytes("Response Data"))
{
CorrelationId = message.CorrelationId,
Label = responseType
};

// Send the response to the specified subscription
var responseClient = new TopicClient(connectionString, topicName);
responseMessage.UserProperties["TargetSubscription"] = message.UserProperties["ReplyTo"].ToString();
await responseClient.SendAsync(responseMessage);
await responseClient.CloseAsync();

// Mark the message as complete
await subscriptionClient.CompleteAsync(message.SystemProperties.LockToken);
}, new MessageHandlerOptions(ExceptionReceivedHandler) { MaxConcurrentCalls = 1, AutoComplete = false });
}

This pattern allows each subscriber to process messages as per the specified filter, enabling a multi-consumer setup where each service only processes the messages relevant to it.

Step 5: Listening for Responses in the Response Subscription

Finally, the requester service listens to the response subscription on the topic, matching responses by CorrelationId to ensure each response corresponds to the correct request.

public async Task ListenForResponsesAsync()
{
var subscriptionClient = new SubscriptionClient(connectionString, topicName, "response-subscription");
subscriptionClient.RegisterMessageHandler(async (message, token) =>
{
if (message.CorrelationId == expectedCorrelationId)
{
var responseData = Encoding.UTF8.GetString(message.Body);
// Process the response data...
}

await subscriptionClient.CompleteAsync(message.SystemProperties.LockToken);
}, new MessageHandlerOptions(ExceptionReceivedHandler) { MaxConcurrentCalls = 1, AutoComplete = false });
}

Step 6: Error Handling and Retry Mechanisms

As with queues, topics support dead-letter queues and retry mechanisms for handling errors gracefully. Configuring these can help manage message failures and ensure reliable processing:

  • Dead-letter Queue: Each subscription has its dead-letter queue, allowing you to track failed messages for each consumer.
  • Retry Policies: Configure retry settings to handle transient issues.

Example retry configuration:

var options = new ServiceBusClientOptions
{
RetryOptions = new ServiceBusRetryOptions
{
MaxRetries = 5,
Delay = TimeSpan.FromSeconds(2)
}
};

Best Practices for Azure Service Bus Topics and Filters

  1. Use Filters to Minimize Traffic: Design filters to ensure each subscriber only processes relevant messages.
  2. Monitor Subscription Metrics: Track metrics in the Azure portal to identify potential issues or imbalances.
  3. Use Dead-Letter Queues: Regularly monitor dead-letter queues for failed messages and reprocess them as needed.

Conclusion

By implementing Azure Service Bus topics with filters for request-response messaging, you can enhance the flexibility and scalability of your distributed system. This pattern enables seamless, asynchronous communication across multiple services, allowing you to support more complex workflows and scale your application with ease.

Azure Service Bus is a powerful choice for achieving resilient, distributed messaging in your microservices architecture. Explore topics, filters, and best practices to make the most out of this messaging solution!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Md hasanuzzzaman
Md hasanuzzzaman

Written by Md hasanuzzzaman

Software Architect | Senior Software Engineer | Backend Developer | Tech Lead | Azure | ASP.NET | Blazor | C# | AI

No responses yet

Write a response