Blog>>Software development>>Enabling the First-In-First-Out pattern in Microsoft Azure Service Bus Queues and Topics

Enabling the First-In-First-Out pattern in Microsoft Azure Service Bus Queues and Topics

Microservices architecture has become the modern standard for building scalable and resilient applications. Breaking down an application into independent services allows each functionality to be developed, deployed, and scaled independently, fostering flexibility and innovation. However, this approach brings challenges in ensuring reliable communication and coordination between services.

One strategy for dealing with such challenges is decoupling services using a message broker like Azure Service Bus      link-icon, which enables asynchronous communication between microservices. This ensures reliable message delivery and processing, even during service outages or heavy loads.

Azure Service Bus can assist in various patterns, but the FIFO (first-in-first-out) pattern stands out. CodiLime Engineers have verified FIFO’s effectiveness in production environments, confirming its reliability. FIFO can cover crucial scenarios like processing financial transactions, managing e-commerce orders, or handling time-critical notifications, where maintaining the sequence of operations is essential to prevent data consistency and integrity of the business logic from slipping away.

In this blog, we will explore how to enable the FIFO pattern within the Azure Service Bus. Specifically, we will:

  • dive into the concept of message sessions with session IDs, 
  • show a practical implementation using Azure Functions and Node.js with ARM templates and .bicep files,
  • discuss best practices for harmonizing your microservices to ensure reliable communication in an orderly manner.

First, let's define some basic concepts to better understand them in advance - Azure Service Bus and the first-in-first-out (FIFO) pattern.

An introduction to Azure Service Bus

Azure Service Bus is a fully managed enterprise message broker      link-icon created by Microsoft. It is designed to support reliable and asynchronous communication between microservices or distributed applications. As a helpful intermediary, Azure Service Bus ensures that messages are delivered, even if the network or one application is down. This is one of the fundamental needs for building resilient and scalable systems.

There are two important definitions worth knowing before learning about Azure Service Bus features:

  • Producer (or Sender) - An application or service that sends messages to the Azure Service Bus. The producer initiates the communication by generating data or events that need to be processed.
  • Consumer (or Receiver) - An application or service that receives and processes messages from the Azure Service Bus.

The key features offered by Azure Service Bus are the following:

  • Queues: Support one-way communication where each message is processed by a single consumer.
  • Topics and Subscriptions: Support one-to-many communication where messages are sent to multiple subscribers.
  • Sessions: Support FIFO to group related messages for ordered processing.
  • Duplicate Detection: This allows the system to identify messages that were already sent to the queue or topic. It is valuable in a scenario where any network issues or application logic error occurs, and the same message is delivered more than once.
  • Dead-Letter Queues: Handle messages that cannot be processed successfully.
  • Retry Mechanism: Azure Service Bus provides an inbuilt policy to automatically retry if a failure in message processing has taken place. This means that if a message cannot be successfully processed by a consumer, it will retry for a previously defined number of times before finally being moved into the dead-letter queue. This way, more reliability and robustness are brought to message processing which can prevent data loss caused by transient errors.

It is worth mentioning that Azure Service Bus currently has three price tiers (Basic, Standard and Premium), and some of the features described above (such as sessions) are not available in the Basic Tier. For more information, please visit Azure Service Bus pricing      link-icon.

First-In-First-Out Pattern: what it is and why it is crucial

When we talk about messaging systems, there is an outstanding concept called FIFO, or first-in-first-out. But what does it mean? Imagine you're at the supermarket checkout. You wouldn't be too happy if someone who arrived after you got served before you, would you? FIFO is the rule that ensures the person who is first in the queue will be served first, without exception.

In the distributed world of systems and messaging, FIFO ensures that messages are processed in the exact order in which they are sent. Azure Service Bus excels at this and supports FIFO using sessions.

So, why is FIFO important?

  • Data integrity 

Many applications rely on the precise order of operations. For instance, financial transactions must be processed sequentially to maintain accurate account balances. Imagine if a withdrawal were processed before the corresponding deposit!

  • Predictability 

Processing messages in the order they are received makes it much easier to predict and understand system behavior. This is especially important in systems where events are closely related and must be handled in a specific sequence.                                

Azure Service Bus has a sessions feature that ensures reliable FIFO processing. Sessions may group related messages so they are handled in the exact order they are delivered and processed. It works like a mini-queue or sub-queue within the larger queue, guaranteeing that all messages will be processed in the specified order.

Using sessions, Azure Service Bus allows you to maintain message order while retaining scalability and reliability within the system. It is like having your cake and eating it too: you get a guarantee of FIFO processing along with all the benefits of a flexible distributed messaging system.

Comparing Azure Service Bus scenarios with and without sessions enabled

Scenario A: Service Bus with the sessions feature disabled

Let’s consider the following scenario:

  • Five messages are in an Azure Service Bus queue, each arriving one millisecond after the previous one.
  • The system uses two consumer instances - Consumer A and Consumer B. Each starts processing a new message only after finishing the previous one. If Consumer A is occupied, the next message will be picked by Consumer B to be processed.
Scenario A: Service Bus with the sessions feature disabled

In the diagram above, the following is happening:

  1. Consumer A picks and processes message 1.
  2. Consumer B picks and processes message 2.
  3. Consumer B finishes processing message 2.
    Consumer A isn't done with processing message 1 yet.
    So, Consumer B picks up message 3.
  4. Consumer B finishes processing message 3.
    Consumer A is still not done with processing message 1 yet.
    Consumer B picks and processes message 4.
  5. Finally, Consumer A completes processing message 1 and picks up message 5.

In this scenario the messages are processed in the order: Message 2, Message 3, Message 4, Message 1, and Message 5.

If your application does not need to process the messages in order, that’s OK. However, if you need Message 1, Message 2 and Message 3… to be processed in order, you need to enable sessions in your Azure Service Bus Queue.

Business case:

Imagine these messages represent transactions for a customer's bank account. Suppose the account balance is $0, and Message 1 is a $1000 deposit, while Messages 2, 3, and 4 are withdrawals. If these Messages are not processed in the order they were sent, the withdrawals might occur before the deposit. This would result in the account showing insufficient funds for the withdrawals, leading to failed transactions or overdraft situations.

By enabling sessions in your Azure Service Bus Queue, you ensure that the messages are processed in the exact order they were sent. This preserves the integrity of transactional operations and maintains accurate account balances.

Scenario B: Service Bus with the sessions feature enabled

In this scenario, as shown in the diagram below, we are consuming messages from a Service Bus Queue with sessions enabled, using an Azure Function with Service Bus Queue binding.

Consumer A and Consumer B are the same functions, but different instances.

Scenario B: Service Bus with the sessions feature enabled

In this case, we could say that the Service Bus Queue internally will have “virtual queues” corresponding to each session to store messages belonging to one session and, consequently, process them in the order they were sent.

Business case:

Imagine these messages represent transactions for two different customers, Customer 1 and Customer 2, and their respective bank accounts.

Assuming that the initial balance of these two bank accounts is $0, let's consider the following transactions for Customer 1 in order:

  1. $1000 deposit (represented by Message 1)
  2. $800 withdrawal (represented by Message 4)

And the transactions performed by Customer 2 are the following, in order:

  1. $1000 deposit (represented by Message 2)
  2. $1000 deposit (represented by Message 3)
  3. $1800 withdrawal (represented by Message 5)

With sessions enabled, we ensure that the transaction messages for both customers are consumed in the order they were performed. This guarantees that both clients will have the correct balance to cover their withdrawals.

For example, in Customer 1's case, the $1000 deposit (Message 1) is processed before the $800 withdrawal (Message 4), ensuring there are sufficient funds for the withdrawal. Similarly, for Customer 2, the two $1000 deposits (Messages 2 and 3) are processed before the $1800 withdrawal (Message 5), ensuring the account has enough balance to cover the withdrawal.

Using sessions in Service Bus Queues with Azure Functions: step-by-step configuration 

In this example, we will use an Azure Function with a Service Bus binding to consume messages sent to a Service Bus Queue.

There are four steps we need to follow:

  1. Creating a Service Bus Queue with sessions enabled
    (description with images below)
  2. Enabling sessions in the Service Bus Queue Consumer
    (We need to have the Consumer in place)
  3. Adding a Queue Timeout to the host.json file
  4. Sending messages to the queue with a specific SessionId

1. Creating a Service Bus Queue with sessions enabled

One important thing to know is that sessions can be enabled only when creating the queue for the very first time. This means that it is not possible to enable sessions if the queue already exists. If you want to enable sessions in an existing queue, you must re-create the queue with the sessions enabled.

You have two options to create the Service Bus Queue with sessions enabled:

  • via Azure Portal,
  • via Azure ARM Templates (using .bicep files).

Creating the Service Bus Queue with sessions enabled via Azure Portal is very straightforward. You just need to select the "Enable Sessions" option when creating the queue, as shown in the image below:

Creating the Service Bus Queue with sessions enabled via Azure Portal

If you want to do it by using Azure ARM Templates (using .bicep files), you will need to set the requiresSession property to true, as shown in the code snippet below (line 10):

note: the idea is to insert a code snippet in the blog post instead of a screenshot

create-service-bus-queue.bicep

param queueName string
param serviceBusName string
param createDeadLetterQueue bool
param maxDeliveryCountValue int = 10

resource queue 'Microsoft.ServiceBus/namespaces/queues@2021-06-01-preview' = {
  name: '${serviceBusName}/${queueName}'
  properties: {
    requiresDuplicateDetection: false
    requiresSession: true
    defaultMessageTimeToLive: 'P5D'
    deadLetteringOnMessageExpiration: createDeadLetterQueue
    enableBatchedOperations: true
    status: 'Active'
    autoDeleteOnIdle: 'P10675199DT2H48M5.4775807S'
    enablePartitioning: false
    enableExpress: false
    maxDeliveryCount: maxDeliveryCountValue
  }
}

2. Enabling sessions in the Service Bus Queue Consumer

In this example, our consumer will be a function within an Azure Function App. This function should have a “serviceBusTrigger” binding and the “isSessionsEnabled” field set to true for this binding in the function.json file of our function, as shown below:

note: the idea is to insert a code snippet in the blog post instead of the screenshot

function.json

{
  "bindings": [
    {
      "name": "ourFunctionConsumer",
      "type": "serviceBusTrigger",
      "direction": "in",
      "queueName": "our-service-bus-queue-name",
      "connection": "our-service-bus-connection-string",
	    "isSessionsEnabled": true
    }
  ],
  "scriptFile": "../dist/ourFunctionConsumer/index.js"
}

3. Adding a Queue Timeout to the host.json file

In Azure Functions, each scaled instance acquires an exclusive lock on one session and processes the messages in that session sequentially.

Each instance will wait for messages in the session for a configurable amount of time. Once this time has elapsed, the exclusive lock for that session will expire, and the scaled instance will be ready to process messages from another session.

This value is configured in the host.json file of our Azure Function App. In the example below, each scaled instance will wait for messages in a session for 30 seconds (line 18).

note: the idea is to insert a code snippet in the blog post instead of a screenshot

host.json

{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[2.*, 3.0.0)"
  },
  "extensions": {
    "serviceBus": {
      "sessionHandlerOptions": {
        "messageWaitTimeout": "00:00:30"
      }
    }
  }
}

4. Sending messages to the queue with a specific SessionId

The sessionId is a unique identifier attached to each message in the Service Bus Queue that allows the Service Bus to group messages that belong to the same session.

This identifier ensures that messages within the same session are delivered to the consumer in the order they were sent, maintaining the sequence of operations.

For example, in our transactions scenario described earlier in this blog post, messages related to Customer 1's account could have a sessionId of "Customer1", while messages related to Customer 2's account could have a sessionId of "Customer2". This way, all messages for a particular customer are processed in the exact order they were sent, preserving the integrity of the transaction sequence.

By adding the sessionId, we ensure that the message is sent to the same session and handled by the same Azure Function instance. 

We could define a session as a “sub-queue” within the Azure Service Bus queue.

note: the idea is to insert a code snippet in the blog post instead of a screenshot

service-bus-client.ts

import { ServiceBusClient, ServiceBusMessage } from '@azure/service-bus';

const connectionString = 'your-service-bus-connection-string';
const queueName = 'your-queue-name';

const sbClient = new ServiceBusClient(connectionString);
const sender = sbClient.createSender(queueName);

const messages: ServiceBusMessage[] = [
	{ body: 'Message 1', sessionId: 'session1' },
	{ body: 'Message 2', sessionId: 'session1' },
	{ body: 'Message 3', sessionId: 'session1' },
];

async function sendMessages() {
	await sender.sendMessages(messages);

	await sender.close();
	await sbClient.close();
}

sendMessages().catch((err) => {
	console.error('Error sending messages: ', err);
});

Conclusion

Enabling the FIFO pattern in Azure Service Bus using sessions is essential for applications where a time-based order of message processing matters. By understanding the key concepts and following the steps outlined above, you can ensure that your microservices communicate reliably and in the correct order.

Whether you’re processing financial transactions, managing e-commerce orders, or handling time-sensitive notifications, Azure Service Bus sessions provide a robust solution for maintaining data consistency and business logic integrity.

Bajo García Francisco

Francisco Bajo García

Frontend Engineer

Francisco Bajo García is a Frontend Engineer and author on CodiLime's blog. Check out the author's articles on the blog.Read about author >

Read also

Get your project estimate

For businesses that need support in their software or network engineering projects, please fill in the form and we'll get back to you within one business day.