NServiceBus: In-order message processing
NServiceBus is like all the other messaging systems: It doesn’t promise to process the messages in-order. This attribute must be kept in mind when designing your application around a message bus. Let’s examine a situation where we have an ASP.NET MVC application with a user registration form. When the user fills the form, the controller can send one or many messages into the bus, depending on what information user has entered. For simplicity’s sake our example will have two different messages: CreateUserCommand and ChangeUserEmailAddressCommand.
Problem description
If our client sends a CreateUserCommand-message followed by a ChangeUserEmailAddressCommand-message and our message bus doesn’t promise to process them in order, how can we be sure that the ChangeUserEmailAddressCommand isn’t processed first? How can we update the user’s email address if the user isn’t in our database yet?
This tutorial uses NServiceBus 2 RTM and ASP.NET MVC 2. There’s a link to the source code at the bottom of this post.
Message handlers
Throughout this tutorial we are using the following message handlers at our server side:
1: public class ChangeUserEmailAddressCommandHandler : IHandleMessages<ChangeUserEmailAddressCommand>
2: {
3: public void Handle(ChangeUserEmailAddressCommand message)
4: {
5: Thread.Sleep(100);
6: Console.WriteLine("###########{0}", message.UserId);
7: }
8: }
and
1: public class CreateUserCommandHandler : IHandleMessages<CreateUserCommand>
2: {
3: public void Handle(CreateUserCommand message)
4: {
5: Thread.Sleep(300);
6: Console.WriteLine("***********{0}", message.UserId);
7: }
8: }
Creating a new user is a more robust operation so it takes a little longer than changing the user’s email address.
Client’s controller
To simulate the stress for our server, our client will send it 100 messages in total, divided in half between the CreateUserCommand- and ChangeUserEmailAddressCommand-messages. The initial code in ASP.NET MVC controller looks like the following:
1: public ActionResult SendMessages()
2: {
3: for (int i = 0; i < 50; i++)
4: {
5: var createUserMessage = new CreateUserCommand() { UserId = i };
6: MvcApplication.Bus.Send(createUserMessage);
7:
8: var changeEmailMessage = new ChangeUserEmailAddressCommand() {UserId = i};
9: MvcApplication.Bus.Send(changeEmailMessage);
10: }
11:
12: return View("Index");
13: }
Pretty simple. We first send a CreateUserCommand-message and then a ChangeUserEmailAddressCommand-message. Both messages have the same user id.
Server’s configuration
This is how our server’s configuration looks like:
<MsmqTransportConfig
InputQueue=<span style="color: #006080">"InputQueue"</span>
ErrorQueue=<span style="color: #006080">"error"</span>
NumberOfWorkerThreads=<span style="color: #006080">"1"</span>
MaxRetries=<span style="color: #006080">"5"</span>
/>
First test
Given the previously described controller, message handlers and the server configuration will give us the following result (copied from server’s console):
Keeping in mind that the lines with ****** represent a CreateUserCommandHandler and the lines with ###### are caused by a ChangeUserEmailAddressCommandHandler, we can see that the messages have been processed in order. Did we get lucky? No. Remember our server’s configuration? In our first test the server is using only one thread (NumberOfWorkerThreads) to process all the messages. Because of this, the NServiceBus handles them in order.
Second test
Our server has a nice quad core processor so we’re wasting it's computing power by limiting it to only one thread. So, for our second test, let’s modify the NumberOfWorkerThreads to 4. Here are the new results:
We have a problem. Our server is trying to change the email address of the user 48 before it has been created in our system. The likely outcome? Exceptions. Because the NServiceBus has now more worker threads, each thread will process a new message from the queue as soon as it has handled the last one. Is there an easy way to make sure that the required messages are processed in-order? Yes, if we can change the client’s code.
Sending messages in-order
Our client’s controller sends the messages one after another, for a total count of 100 calls to Bus.Send. But the Send-method also has an overload which takes an array of IMessages as a parameter. By putting the two relevant messages in an array, in the correct order, we can be sure that the NServiceBus will process them in order. The messages sent in an array will be processed by one thread by the server.
Third test
Here’s our new client code:
1: public ActionResult SendMessagesInOrder()
2: {
3: for (int i = 0; i < 50; i++)
4: {
5: var createUserMessage = new CreateUserCommand() { UserId = i };
6: var changeEmailMessage = new ChangeUserEmailAddressCommand() { UserId = i };
7:
8: MvcApplication.Bus.Send(new IMessage[] {createUserMessage, changeEmailMessage});
9: }
10:
11: return View("Index");
12: }
This time the two messages are packed in an array (a batch) and the Bus.Send is called only 50 times. The results are interesting:
NServiceBus makes sure that the ChangeUserEmailAddressCommand-message is always handled after the CreateUserCommand-message. Fixing our problem was rather easy because we we’re able to change our client’s code. But what can we do if we can’t alter the client?
Other option
One of the easiest solution is to change our ChangeUserEmailAddressCommand to call Bus.HandleCurrentMessageLater-method if the user isn’t in our system yet. This will send the original message back to the input queue. Because it ends up as the last message in the queue, it’s highly probable that the system has been able to process the relevant CreateUserCommand-message before the message is processed the second time. But make sure to create a failure system so that the same message doesn’t end up in an endless loop. At some point it has to be moved into an error queue.
Conclusion
NServiceBus doesn’t handle messages in-order. It’s up to the developer to make sure that the relevant messages are handler in the right order. The easiest way to do this is to send messages as batches from the client.
Download
You can get a sample application from the GitHub.