Wednesday, August 18, 2010

Getting Started with NServiceBus Part 2: Music Store Schema

Now that we've identified our architecture, the next order of business is to identify what information will be shared within the system.  In our example we will be sending commands to the server to update shopping carts and to place orders.  We'll need two classes to house that information.  First we'll take a look into adding something to a cart by looking at the ShoppingCartController class.  We quickly find the add method:

 public ActionResult AddToCart(int id)
 {
          ...
            cart.AddToCart(addedAlbum);
          ...
 }

This leads us to the AddToCart method:

 public void AddToCart(Album album)
 {
            ...
            if (cartItem == null)
            {
                // Create a new cart item
                cartItem = new Cart
                {
                    AlbumId = album.AlbumId,
                    CartId = shoppingCartId,
                    Count = 1,
                    DateCreated = DateTime.Now
                };
                storeDB.AddToCarts(cartItem);
            }
            else
            {
                // Add one to the quantity
                cartItem.Count++;
            }

            // Save it
            storeDB.SaveChanges();
 }

We find that all we need is the id of the album and the id of the cart to add it to, and therefore we can define our command as:

public interface IAddToCartCommand : ICommand
{
        String CartId { get; set; }
        Int32 AlbumId { get; set; }
}

public interface ICommand : IMessage
{
}

In order to share the reference to the Cart class we have to pull the Models namespace into a separate assembly out of the uber assembly the sample ships with.  There are three major things to note in this code.

First of all in order to identify a message to the NServiceBus infrastructure, we must mark the message with the IMessage interface.  This is how NSB will wire messages to message handlers.

The second note is that we've created an intermediary interface.  We have done this so we can easily differentiate between commands and events.  Commands denote one-way, point to point communication between known parties.  Events denote one-way, one to many communication with potentially unknown parties.  The other way we differentiate the two is to change how the verb in the name is used.  Typically commands will tell the server to do something, ex. "AddToCart".  An event will let us know something happened in the past, ex. "ItemAddedToCart".

Lastly we always use interfaces to define our message schema.  If we do so then NSB can gracefully handle the versioning of messages for us.  This becomes very important when we change our messages and we have to maintain backwards compatibility.

Now we can look at placing an order.  The code for this is a bit strange as its broken up into two different transactions, one for the "header" and one for the details.  The first part is in the AddressAndPayment method in the CheckoutController class, and the second part is in the ShoppingCart class:

        public ActionResult AddressAndPayment(FormCollection values)
        {
            var order = new Order();
            ...
                    order.Username = User.Identity.Name;
                    order.OrderDate = DateTime.Now;

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

                    //Process the order
                    var cart = ShoppingCart.GetCart(this.HttpContext);
                    cart.CreateOrder(order);

                    return RedirectToAction("Complete",
                        new { id = order.OrderId });
               ...
        }


 public int CreateOrder(Order order)
 {
            ...
            var cartItems = GetCartItems();

             foreach (var cartItem in cartItems)
            {
                var orderDetails = new OrderDetail
                {
                    AlbumId = cartItem.AlbumId,
                    OrderId = order.OrderId,
                    UnitPrice = cartItem.Album.Price
                };

                storeDB.OrderDetails.AddObject(orderDetails);
                ...
            }

            //Save the order
            storeDB.SaveChanges();
            ...
            //Return the OrderId as a confirmation number
            return order.OrderId;
}

In order to preserve consistency in our database, we'll perform all these actions in a single transaction.  This way we don't get orders without their details.  Also by using durable messaging, we'll ensure that orders don't get lost.  Within the code, the Order is constructed from the ShoppingCart object.  Therefore, all we need is the id of the current cart, and we can look up the rest of the information server side(also reducing trips to the server):

public interface IPlaceOrderCommand : ICommand
{
        String CartId { get; set; }
}


In summary we've decided on how our schema is going to look so that we can now start putting these messages On the Bus!  Next time we'll change the store front to put messages on the bus.  Code can be found here

No comments:

Post a Comment