Monday, September 6, 2010

Getting Started with NServiceBus Part 4: Handling Messages

Last time we plugged NSB into the Music Store web app and configured a one way method communication to the server. From there, we'll pick up the messages and start processing them on the server side. The first bit of setup we need to do is to create a new class library and add a reference to our Messages assembly and also all of the NSB assemblies.

First we'll add an configuration file and new class named "EndpointConfig" to handle all of our configuration needs. Let's start with the app.config. In the app config we'll need to configure how clients will send us messages by using the MsmqTransportConfig section. Since we'll be interacting with the Music Store database, we'll need connection strings as well. Here is the app.config:
  
    
The MsmqTransportConfig section defines four main properties. The InputQueue property tells our web app where to put the messages. The ErrorQueue property is where our messages will go if they cannot be processed after the configured amount of retries. The number of retries is configured using the MaxRetries property. The NumberOfWorkerThreads specifies how many threads will be spun up to read messages from the queue and process them. Initially you should start with one and ramp the threads up from there if necessary.

The next piece of the configuration puzzle is the EndpointConfig class. This class will get picked up by NSB at startup and will be used to instruct NSB how to configure the end point.
    public class EndpointConfig : IConfigureThisEndpoint, AsA_Server
    {
    }
The IConfigureThisEndpoint is an interface that marks the class for NSB to pick up during configuration. The AsA_Server interface tells NSB to configure an endpoint that is transactional and that does not purge messages. Now that we have NSB configured, we can start getting into the message handlers.

In order to handle messages, you must implement the IHandleMessages<T> interface. T in this case will be the interface your message type is based upon. Let's check out the AddToCartHandler:
 public class AddToCartHandler : IHandleMessages
    {
        public void Handle(IAddToCartCommand message)
        {
            MusicStoreEntities storeDB = new MusicStoreEntities();

            // Retrieve the album from the database
            var addedAlbum = storeDB.Albums
                .Single(album => album.AlbumId == message.AlbumId);

            // Add it to the shopping cart
            var cart = new ShoppingCart( message.CartId );

            cart.AddToCart(addedAlbum);
        }
    }
Note that the method is void since we are processing the messages in a completely different app domain that the web site. Once the interface has been implemented, it is straight .NET programming from there. We do the same for the PlaceOrderHandler:
public void Handle(IPlaceOrderCommand message)
        {
            MusicStoreEntities storeDB = new MusicStoreEntities();

            var order = new MvcMusicStore.Models.Order();

            order.Username = message.UserId;
            order.OrderDate = DateTime.Now;
            order.OrderId = message.OrderId;

            // Save Order
            storeDB.AddToOrders(order);
            storeDB.SaveChanges();

            //Process the order
            var cart = new ShoppingCart(message.CartId);

            cart.CreateOrder(order);
        }
Note that we are taking the order id from the client. This is because we must show the user the order id right after they have ordered on the client side. We have to modify the underlying data model in order to accommodate this requirement. Simply edit the Order and Order Details tables in the models so that their ids are not generated by the database. Lastly, to get our server up and running we need to edit the Debug properties of the project. First build the project and then set the Debug session to start using an external program. Point to the generic host that comes with NSB, NServiceBus.Host.exe(in the bin directory).

Now we can start adding things to our cart and placing orders from the UI. What you'll notice right away is that when we add things to the cart, it shows us the cart. Under normal conditions(Request/Response design) this would be ok, but our album may not have been saved to the database yet. Therefore we need to change the UI so that it shows the album from the client side instead of the server side. We can do this by creating a new action and showing everything client side:
public ActionResult AddedItemToCart(int id)
        {
            var cart = ShoppingCart.GetCart(this.HttpContext);

            // Retrieve the album from the database
            var addedAlbum = storeDB.Albums
                .Single(album => album.AlbumId == id);

            // Set up our ViewModel
            var viewModel = new ShoppingCartViewModel
            {
                CartItems = new System.Collections.Generic.List(),
                CartTotal = cart.GetTotal() + addedAlbum.Price
            };

            viewModel.CartItems.Add(new Cart { Album = addedAlbum, AlbumId = addedAlbum.AlbumId, Count = 1 });

            return View(viewModel);
        }
All we have to do now is redirect to our new action instead of the old one(Index):
// Go back to the main store page for more shopping
return RedirectToAction("AddedItemToCart", new { id = addedAlbum.AlbumId } );
You'll also note that you may get an error when placing an order. The Music Store immediately validates against the database that your order is there. This is now an unnecessary step because via messaging we are guaranteeing the delivery of the order. All we need to do is simply comment out all the validation code:
public ActionResult Complete(int id)
        {
            //// Validate customer owns this order
            //bool isValid = storeDB.Orders.Any(
            //    o => o.OrderId == id &&
            //    o.Username == User.Identity.Name);

            //if (isValid)
            //{
            //    return View(id);
            //}
            //else
            //{
            //    return View("Error");
            //}

            return View(id);
        }
Now with our minor tweaks to the UI we have fully implemented NSB in our Music Store application. We can guarantee that our users will be able to shop and place orders even if our database is down. This becomes increasingly important especially for those big spenders, we don't want to have to tell them to start all over and that we lost their order.

Full code can be found here

4 comments:

  1. Thanks for sharing this example. I've been wanting to implement something similiar, however, I'm struggling to justify to the team why this approach is favored over a more traditional approach.

    Could you provide a few reasons why this approach of publishing to NServiceBus is better than just directly writing to the database and returning the data?

    ReplyDelete
  2. It really depends on your situation. In this case it's a question of customer service. We don't want to lose an important customer's order or make them re-enter it. We also don't ever want customers to not be able to shop and place orders in our store. If we take the RPC approach, none of this is guaranteed. There are a lot of other reasons, performance, availability, etc., for a detailed description look at this Command/Query Segregation article: http://www.udidahan.com/2009/12/09/clarified-cqrs/

    ReplyDelete
  3. Ok so i agree with your idea of having a messaging mechanism such as NSB so that the customers are able to place their orders and checkout, but imagine a scenario where the database is down for a longer period of time and your messages are comming with very frequent basis. So i have few questions regarding this case
    1) If the rate of messages comming is faster that the rate at which they are processed, does an overflow occurs in the queue?If yes, How would you prevent overflow in that case?
    2) How does NSB let us specify the queue storage capacity.
    3)In the event that the database is down for a long time,how does the handling service tackles to that scenario ? I suppose it sends some sort of an error response back to the sender, upon which the sender retries based on the number of retries configured. The message is ultimately moved to the error queue. So i guess there is a chance for a customer order to not get processed.

    Regards

    ReplyDelete
  4. NSB does not allocate/monitor MSMQ storage, that is left up to our administrator friends. We actually exceeded our disk in a test system, but since the messages are recoverable, they were still on the disk and able to be reprocessed. If a database goes down the messages must be reprocessed from the error queue. I recommend having something monitoring all queues.

    ReplyDelete