Introduction
I’m currently working on broadening the content in the Windows Azure Service Bus Developer Guide. One of the features I have been looking at over the past week is the support for transactional messaging. When using the direct programming model and the WCF interface some, but not all, messaging operations can participate in transactions. This allows developers to improve the reliability of messaging systems. There are some limitations in the transactional model, transactions can only include one top level messaging entity (such as a queue or topic, subscriptions are no top level entities), and transactions cannot include other systems, such as databases.
As the transaction model is currently not well documented I have had to figure out how things work through experimentation, with some help from the development team to confirm any questions I had. Hopefully I’ve got the content mostly correct, I will update the content in the e-book if I find any errors or improvements that can be made (any feedback would be very welcome). I’ve not had a chance to look into the code for transactions and asynchronous operations, maybe that would make a nice challenge lab for my Windows Azure Service Bus course.
Transactional Messaging
Messaging entities in the Windows Azure Service Bus provide support for participation in transactions. This allows developers to perform several messaging operations within a transactional scope, and ensure that all the actions are committed or, if there is a failure, none of the actions are committed. There are a number of scenarios where the use of transactions can increase the reliability of messaging systems.
Using TransactionScope
In .NET the TransactionScope class can be used to perform a series of actions in a transaction. The using declaration is typically used de define the scope of the transaction. Any transactional operations that are contained within the scope can be committed by calling the Complete method. If the Complete method is not called, any transactional methods in the scope will not commit.
// Create a transactional scope.
using (TransactionScope scope = new TransactionScope())
{
// Do something.
// Do something else.
// Commit the transaction.
scope.Complete();
}
In order for methods to participate in the transaction, they must provide support for transactional operations. Database and message queue operations typically provide support for transactions.
Transactions in Brokered Messaging
Transaction support in Service Bus Brokered Messaging allows message operations to be performed within a transactional scope; however there are some limitations around what operations can be performed within the transaction.
In the current release, only one top level messaging entity, such as a queue or topic can participate in a transaction, and the transaction cannot include any other transaction resource managers, making transactions spanning a messaging entity and a database not possible.
When sending messages, the send operations can participate in a transaction allowing multiple messages to be sent within a transactional scope. This allows for “all or nothing” delivery of a series of messages to a single queue or topic.
When receiving messages, messages that are received in the peek-lock receive mode can be completed, deadlettered or deferred within a transactional scope. In the current release the Abandon method will not participate in a transaction. The same restrictions of only one top level messaging entity applies here, so the Complete method can be called transitionally on messages received from the same queue, or messages received from one or more subscriptions in the same topic.
Sending Multiple Messages in a Transaction
A transactional scope can be used to send multiple messages to a queue or topic. This will ensure that all the messages will be enqueued or, if the transaction fails to commit, no messages will be enqueued.
An example of the code used to send 10 messages to a queue as a single transaction from a console application is shown below.
QueueClient queueClient = messagingFactory.CreateQueueClient(Queue1);
Console.Write("Sending");
// Create a transaction scope.
using (TransactionScope scope = new TransactionScope())
{
for (int i = 0; i < 10; i++)
{
// Send a message
BrokeredMessage msg = new BrokeredMessage("Message: " + i);
queueClient.Send(msg);
Console.Write(".");
}
Console.WriteLine("Done!");
Console.WriteLine();
// Should we commit the transaction?
Console.WriteLine("Commit send 10 messages? (yes or no)");
string reply = Console.ReadLine();
if (reply.ToLower().Equals("yes"))
{
// Commit the transaction.
scope.Complete();
}
}
Console.WriteLine();
messagingFactory.Close();
The transaction scope is used to wrap the sending of 10 messages. Once the messages have been sent the user has the option to either commit the transaction or abandon the transaction. If the user enters “yes”, the Complete method is called on the scope, which will commit the transaction and result in the messages being enqueued. If the user enters anything other than “yes”, the transaction will not commit, and the messages will not be enqueued.
Receiving Multiple Messages in a Transaction
The receiving of multiple messages is another scenario where the use of transactions can improve reliability. When receiving a group of messages that are related together, maybe in the same message session, it is possible to receive the messages in the peek-lock receive mode, and then complete, defer, or deadletter the messages in one transaction. (In the current version of Service Bus, abandon is not transactional.)
The following code shows how this can be achieved.
using (TransactionScope scope = new TransactionScope())
{
while (true)
{
// Receive a message.
BrokeredMessage msg = q1Client.Receive(TimeSpan.FromSeconds(1));
if (msg != null)
{
// Wrote message body and complete message.
string text = msg.GetBody<string>();
Console.WriteLine("Received: " + text);
msg.Complete();
}
else
{
break;
}
}
Console.WriteLine();
// Should we commit?
Console.WriteLine("Commit receive? (yes or no)");
string reply = Console.ReadLine();
if (reply.ToLower().Equals("yes"))
{
// Commit the transaction.
scope.Complete();
}
Console.WriteLine();
}
Note that if there are a large number of messages to be received, there will be a chance that the transaction may time out before it can be committed. It is possible to specify a longer timeout when the transaction is created, but It may be better to receive and commit smaller amounts of messages within the transaction.
It is also possible to complete, defer, or deadletter messages received from more than one subscription, as long as all the subscriptions are contained in the same topic. As subscriptions are not top level messaging entities this scenarios will work.
The following code shows how this can be achieved.
try
{
using (TransactionScope scope = new TransactionScope())
{
// Receive one message from each subscription.
BrokeredMessage msg1 = subscriptionClient1.Receive();
BrokeredMessage msg2 = subscriptionClient2.Receive();
// Complete the message receives.
msg1.Complete();
msg2.Complete();
Console.WriteLine("Msg1: " + msg1.GetBody<string>());
Console.WriteLine("Msg2: " + msg2.GetBody<string>());
// Commit the transaction.
scope.Complete();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Unsupported Scenarios
The restriction of only one top level messaging entity being able to participate in a transaction makes some useful scenarios unsupported. As the Windows Azure Service Bus is under continuous development and new releases are expected to be frequent it is possible that this restriction may not be present in future releases.
The first is the scenario where messages are to be routed to two different systems.
The following code attempts to do this.
try
{
// Create a transaction scope.
using (TransactionScope scope = new TransactionScope())
{
BrokeredMessage msg1 = new BrokeredMessage("Message1");
BrokeredMessage msg2 = new BrokeredMessage("Message2");
// Send a message to Queue1
Console.WriteLine("Sending Message1");
queue1Client.Send(msg1);
// Send a message to Queue2
Console.WriteLine("Sending Message2");
queue2Client.Send(msg2);
// Commit the transaction.
Console.WriteLine("Committing transaction...");
scope.Complete();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
The results of running the code are shown below.
When attempting to send a message to the second queue the following exception is thrown:
No active Transaction was found for ID ’35ad2495-ee8a-4956-bbad-eb4fedf4a96e:1′. The Transaction may have timed out or attempted to span multiple top-level entities such as Queue or Topic. The server Transaction timeout is: 00:01:00..TrackingId:947b8c4b-7754-4044-b91b-4a959c3f9192_3_3,TimeStamp:3/29/2012 7:47:32 AM.
Another scenario where transactional support could be useful is when forwarding messages from one queue to another queue. This would also involve more than one top level messaging entity, and is therefore not supported.
Another scenario that developers may wish to implement is performing transactions across messaging entities and other transactional systems, such as an on-premise database. In the current release this is not supported.
Workarounds for Unsupported Scenarios
There are some techniques that developers can use to work around the one top level entity limitation of transactions. When sending two messages to two systems, topics and subscriptions can be used. If the same message is to be sent to two destinations then the subscriptions would have the default subscriptions, and the client would only send one message. If two different messages are to be sent, then filters on the subscriptions can route the messages to the appropriate destination. The client can then send the two messages to the topic in the same transaction.
In scenarios where a message needs to be received and then forwarded to another system within the same transaction topics and subscriptions can also be used. A message can be received from a subscription, and then sent to a topic within the same transaction. As a topic is a top level messaging entity, and a subscription is not, this scenario will work.
Print | posted on Monday, April 02, 2012 9:46 AM | Filed Under [ BizTalkAzureAppFabicWindows Azure Service Bus ]