Tuesday, September 21, 2010
Unit Testing NSB with VS2010, .NET 4.0, and MSTest
Unit testing NSB can be done without NUnit. For those of you using MSTest, you will have to download the latest trunk and compile NSB yourself to get this to work. Otherwise you will get a bunch of TypeAccessExceptions amongst others. The big difference on the trunk is that the Rhino Mocks reference has been pulled out which I think was causing the issue.
Sunday, September 19, 2010
Getting Started with NServiceBus Part 5: MVC Music Store Basic Pub/Sub
To recap, thus far we've implemented a basic Command/Query Segregation pattern in the ASP.NET MVC Music Store application. This has decoupled us from the UI for performing important operations which include allowing users to shop and place orders.
Now we are going to expand upon our application to provide the facility to ship products. The way we'll do this is by publishing a message when an Order has been accepted. Publishing a message is fundamentally different than sending a message. When you publish a message it is intended to be received by multiple parties. Also you must be aware that there can only be one logical Publisher. If you have multiple Publishers publishing the same message, then the system does not know who to listen to. In our example we'll initially only have one Subscriber, the Shipping service. The Shipping service will handle updating inventory and creating shipping notes for the shipping department. In future installments we'll add more Subscribers to our Order Accepted message.
To get started we have to enhance our data model a bit so we have some place to store our inventory and our shipping notes. I found the hard way that it is significantly easier to update the database and then the model when using the Entity Framework. That being said, we'll add the new tables to the database and then update the model:
We start with our table of inventory positions, which tells us how much of the product we have on hand and how much we need to order. When an order comes in, we'll check this table to see if we have the product, and if we do we'll add that to the Shipped Quantity on our shipping note. If we don't have the product, we'll put it on back order on our note.
The first thing we need to do is modify our Music Store command handler to become a Publisher. This is a change to our EndpointConfig.cs which previously was configured AsA_Server. We'll change this to be configured AsA_Publisher. So what does this do for us? First and foremost it tells the endpoint to expect to receive not only the messages we've defined, but also subscription messages. When a Subscriber starts up, it will put a subscription message into the Publishers input queue, the SAME queue it is currently handling commands upon. The AsA_Publisher configuration also configures NSB to handle things like how to store the subscription messages. For the Lite profile, subscriptions are held in memory, for Integration the messages are stored in a queue, and for Production the messages are stored in a database(leveraging NHibernate). Below is our small tweak to the endpoint configuration:
Now we have a message to publish, we can update our IPlaceOrderHandler. The first thing we need to do is add a public property of type IBus so that NSB will inject a reference to the bus into our handler. After we have the reference, at the end of the handler we call Bus.Publish and provide an instance of the message using an Action:
Now we are going to expand upon our application to provide the facility to ship products. The way we'll do this is by publishing a message when an Order has been accepted. Publishing a message is fundamentally different than sending a message. When you publish a message it is intended to be received by multiple parties. Also you must be aware that there can only be one logical Publisher. If you have multiple Publishers publishing the same message, then the system does not know who to listen to. In our example we'll initially only have one Subscriber, the Shipping service. The Shipping service will handle updating inventory and creating shipping notes for the shipping department. In future installments we'll add more Subscribers to our Order Accepted message.
To get started we have to enhance our data model a bit so we have some place to store our inventory and our shipping notes. I found the hard way that it is significantly easier to update the database and then the model when using the Entity Framework. That being said, we'll add the new tables to the database and then update the model:
We start with our table of inventory positions, which tells us how much of the product we have on hand and how much we need to order. When an order comes in, we'll check this table to see if we have the product, and if we do we'll add that to the Shipped Quantity on our shipping note. If we don't have the product, we'll put it on back order on our note.
The first thing we need to do is modify our Music Store command handler to become a Publisher. This is a change to our EndpointConfig.cs which previously was configured AsA_Server. We'll change this to be configured AsA_Publisher. So what does this do for us? First and foremost it tells the endpoint to expect to receive not only the messages we've defined, but also subscription messages. When a Subscriber starts up, it will put a subscription message into the Publishers input queue, the SAME queue it is currently handling commands upon. The AsA_Publisher configuration also configures NSB to handle things like how to store the subscription messages. For the Lite profile, subscriptions are held in memory, for Integration the messages are stored in a queue, and for Production the messages are stored in a database(leveraging NHibernate). Below is our small tweak to the endpoint configuration:
public class EndpointConfig : IConfigureThisEndpoint, AsA_Server { }The next thing we need to do is actually publish the message. To accomplish this, we'll modify the PlaceOrderHandler to publish a message after it has successfully saved the order. The message we'll publish is the IOrderAcceptedEvent message:
public interface IOrderAcceptedEvent : IEvent { Int32 OrderId { get; set; } }This message has been added to our public schema project(Messages.csproj). All we pass along here is the order id. We can look up the Order on the other side when we go to ship the product, so there is no need to publish the entire Order. There may be a reason to publish the entire order in the future, especially if there exists components that don't have a way to query for Orders. Note that I'm using the naming convention of provided an "Event" suffix. This is so that I can easily know what is an Event versus what is a Command(namespacing would also do).
Now we have a message to publish, we can update our IPlaceOrderHandler. The first thing we need to do is add a public property of type IBus so that NSB will inject a reference to the bus into our handler. After we have the reference, at the end of the handler we call Bus.Publish and provide an instance of the message using an Action:
public class IPlaceOrderHandler : IHandleMessages<IPlaceOrderCommand> { public IBus Bus { get; set; } 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); this.Bus.Publish<IOrderAcceptedEvent>( o => o.OrderId = order.OrderId); } }Now we have our handler publishing messages out to the bus for all subscribers. Next we'll build up our Shipping Subscriber. Add a new project and configure the endpoint AsA_Server just like our original command handler. Next we'll take a look at how we reference the Publisher. We need to know where to drop off our subscription messages. In our subscription message is the Subscriber address along with some other endpoint information. By giving our address to the Publisher, it knows where to push published messages to. To achieve this we add a reference to the Publisher in our app.config:
The overall pub/sub semantics goes something like this:
- Publisher starts up and loads subscriptions from storage(memory, queue, or DB)
- Subscriber starts up and sends a subscription message to the Publisher. If the Publisher already has stored the subscription, it gets ignored. If it does not have the subscription, store the message.
- Publisher makes a call to Bus.Publish.
- Publisher looks to see who is subscribed to that message. It creates a message for each Subscriber and puts it on its outbound queue(this is an internal queue to MSMQ that you don't see unless it is sending the machine to another machine)
- MSMQ takes over and pushes the messages to their destination queues.
- If the subscriber is up and running it will receive the message and call the appropriate handler. If not, the message remains in the subscriber input queue.
Please note that if you have a lot of Subscribers and their queues are down, the messages will build up on the Publishers' outbound queue so you will have to size your storage appropriately.
The last thing we need to do is implement the logic for the Shipping service. We will hydrate the order from the order id in the message and then check our inventory. If we have the product, we ship it, otherwise we back order it. From there our processing is complete and we have a full implementation of Pub/Sub:
public class ShippingHandler : IHandleMessages<IOrderAcceptedEvent> { #region IMessageHandler<IOrderAcceptedEvent> Members public void Handle(IOrderAcceptedEvent message) { MusicStoreEntities storeDB = new MusicStoreEntities(); var order = storeDB.Orders.Single(o => o.OrderId == message.OrderId); var shipNote = new ShippingNote { FirstName = order.FirstName, LastName = order.LastName, Address = order.Address, City = order.City, State = order.State, PostalCode = order.PostalCode }; foreach (var detail in order.OrderDetails) { var inventoryPosition = storeDB.InventoryPositions.Single(p => p.Album.AlbumId == detail.AlbumId); if (inventoryPosition.BalanceOnHand >= detail.Quantity) { inventoryPosition.BalanceOnHand -= detail.Quantity; shipNote.ShippedQuantity += detail.Quantity; } else { shipNote.BackOrderQuantity = detail.Quantity - shipNote.ShippedQuantity; } } storeDB.AddToShippingNotes(shipNote); storeDB.SaveChanges(); } #endregion }
Code is at github
Wednesday, September 8, 2010
Fix It! Where did my project startup settings go?
One thing you'll notice pretty quickly when using NSB and source control is that your Debug settings get lost once you check-in.
This is because these settings are in the $project.user file and not part of the actual $project.csproj file. To fix this, simply add the properties to a PropertyGroup in your project file:
This is because these settings are in the $project.user file and not part of the actual $project.csproj file. To fix this, simply add the properties to a PropertyGroup in your project file:
Program $(ProjectDir)$(OutputPath)NServiceBus.Host.exe NServiceBus.Integration
Tuesday, September 7, 2010
Fix It! NServiceBus Host Crashes with TNS-less Oracle Connections
Come to find out if you are connecting to Oracle with a TNS-less connection, DTC gets all nutty and crashes your NSB host. The fix is to use TNS. This may not be ideal for some, but for most cases the abstraction layer from the application is nice.
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 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.
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:
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:
Full code can be found here
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 : IHandleMessagesNote 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(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); } }
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.ListAll we have to do now is redirect to our new action instead of the old one(Index):(), CartTotal = cart.GetTotal() + addedAlbum.Price }; viewModel.CartItems.Add(new Cart { Album = addedAlbum, AlbumId = addedAlbum.AlbumId, Count = 1 }); return View(viewModel); }
// 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
Subscribe to:
Posts (Atom)