Overview of Service Bus transaction processing

This article discusses the transaction capabilities of Microsoft Azure Service Bus. Much of the discussion is illustrated by the AMQP Transactions with Service Bus sample. This article is limited to an overview of transaction processing and the send via feature in Service Bus, while the Atomic Transactions sample is broader and more complex in scope.

Note

The basic tier of Service Bus doesn't support transactions. The standard and premium tiers support transactions. For differences between these tiers, see Service Bus pricing.

Transactions in Service Bus

A transaction groups two or more operations together into an execution scope. By nature, such a transaction must ensure that all operations belonging to a given group of operations either succeed or fail jointly. In this respect transactions act as one unit, which is often referred to as atomicity.

Service Bus is a transactional message broker and ensures transactional integrity for all internal operations against its message stores. All transfers of messages inside of Service Bus, such as moving messages to a dead-letter queue or automatic forwarding of messages between entities, are transactional. As such, if Service Bus accepts a message, it has already been stored and labeled with a sequence number. From then on, any message transfers within Service Bus are coordinated operations across entities, and will neither lead to loss (source succeeds and target fails) or to duplication (source fails and target succeeds) of the message.

Service Bus supports grouping operations against a single messaging entity (queue, topic, subscription) within the scope of a transaction. For example, you can send several messages to one queue from within a transaction scope, and the messages will only be committed to the queue's log when the transaction successfully completes.

Operations within a transaction scope

The operations that can be performed within a transaction scope are as follows:

Receive operations are not included, because it is assumed that the application acquires messages using the ReceiveMode.PeekLock mode, inside some receive loop or with an OnMessage callback, and only then opens a transaction scope for processing the message.

The disposition of the message (complete, abandon, dead-letter, defer) then occurs within the scope of, and dependent on, the overall outcome of the transaction.

Transfers and "send via"

To enable transactional handover of data from a queue or topic to a processor, and then to another queue or topic, Service Bus supports transfers. In a transfer operation, a sender first sends a message to a transfer queue or topic, and the transfer queue or topic immediately moves the message to the intended destination queue or topic using the same robust transfer implementation that the autoforward capability relies on. The message is never committed to the transfer queue or topic's log in a way that it becomes visible for the transfer queue or topic's consumers.

The power of this transactional capability becomes apparent when the transfer queue or topic itself is the source of the sender's input messages. In other words, Service Bus can transfer the message to the destination queue or topic "via" the transfer queue or topic, while performing a complete (or defer, or dead-letter) operation on the input message, all in one atomic operation.

If you need to receive from a topic subscription and then send to a queue or topic in the same transaction, the transfer entity must be a topic. In this scenario, start transaction scope on the topic, receive from the subscription with in the transaction scope, and send via the transfer topic to a queue or topic destination.

See it in code

To set up such transfers, you create a message sender that targets the destination queue via the transfer queue. You also have a receiver that pulls messages from that same queue. For example:

var connection = new ServiceBusConnection(connectionString);

var sender = new MessageSender(connection, QueueName);
var receiver = new MessageReceiver(connection, QueueName);

A simple transaction then uses these elements, as in the following example. To refer the full example, refer the source code on GitHub:

var receivedMessage = await receiver.ReceiveAsync();

using (var ts = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
    try
    {
        // do some processing
        if (receivedMessage != null)
            await receiver.CompleteAsync(receivedMessage.SystemProperties.LockToken);

        var myMsgBody = new MyMessage
        {
            Name = "Some name",
            Address = "Some street address",
            ZipCode = "Some zip code"
        };

        // send message
        var message = myMsgBody.AsMessage();
        await sender.SendAsync(message).ConfigureAwait(false);
        Console.WriteLine("Message has been sent");

        // complete the transaction
        ts.Complete();
    }
    catch (Exception ex)
    {
        // This rolls back send and complete in case an exception happens
        ts.Dispose();
        Console.WriteLine(ex.ToString());
    }
}

Timeout

A transaction times out after 2 minutes. The transaction timer starts when the first operation in the transaction starts.

Next steps

See the following articles for more information about Service Bus queues: