The ShoppingCart class in the MVC Music Store is my current punching bug, I really don’t like it.
You can see how it looks on the image on the right. The problem is that this code mixes two different responsibilities:
- Operations about a shopping cart:
- GetCart
- GetCartId
- GetCartItems
- GetCount
- Operations of a shopping cart:
- AddToCart
- CreateOrder
- EmptyCart
- MigrateCart
- RemoveFromCart
You might have noticed that all the operations about a shopping cart are get operations. All the operations of the cart are things that belong to the cart, it is the cart’s business logic and reason for being. The Get operations don’t belong to the cart, they belong in some other object that manages instances of carts.
In most applications, we would call this object a Repository. I am not sure that we really need one here. Looking at the Get methods, most of them are here because of the decision to only store cart line items, which requires us to issue explicit queries to get the data.
With Raven, we would follow a different model, which means that the only thing we are likely to need is GetCart() and maybe GetCartId().
Here is how a typical cart document will look like as a document:
And as an entity in our application:
The GetTotal method was replaced with a Total property. Until the GetTotal method, with issued a query to the database, this property operates solely in memory. This is another major difference with Raven vs. OR/M solution is that Raven doesn’t do lazy loading. This is by design, since document dbs data models rarely need to traverse data outside their own document. Traversing the document from Raven cannot force lazy loading or result in the dreaded SELECT N+1 issues.
And now let us deal with the operations about a cart. The most important ones are GetCartId and GetCart. I think that those methods has no place there. I created a new class, ShoppingCartFinder, which looks like this:
Note that we don’t expose GetCartId anymore, this is an internal detail that shouldn’t be seen by clients of this class. We do need to support setting the cart id, because we also support cart migrations (when an anonymous users logs in). We don’t need any of the other methods, so I removed them.
Let us go over the operations of the cart.
The method on the left is the original code, and on the right you can see Raven’s code. The Raven code operates completely in memory, and in totally persistence ignorance. The old code is deals explicitly with persistence. This isn’t that much of a problem, except that this is the wrong level to deal with persistence issues.
Going over RemoveFromCart, you can see that it shrunk significantly in size, and again, it is an in memory operation only. EmptyCart isn’t implemented in the Raven version, since it is just a Lines.Clear()
It is interesting to note that EmptyCart in the old implementation would result in N queries, where N is the number of items in the cart, while with Raven, this will result in 1 query.
I don’t think that there is much to say here, except that the old code would execute N*2 queries, while Raven’s code will still execute 1 query :-)
MigrateCart is interesting, because the implementation is drastically different:
With the old code, we update all the items in the cart, one at a time. With Raven, we do something drastically different. The Shopping Cart Id is the document key, so given the shopping cart id (which is the user name or stored in the session), we can load the shopping cart in using a Load (by primary key, to equate to the relational mindset). Migrating a cart is a simple enough operation, all you have to do is change the key. Since Raven doesn’t allow renames, we do it with a Delete/Store, which are executed inside a single transaction.
The calling code for MigrateCart looks like this:
Note that SaveChanges is atomic and transactional, so this has the same effect as issuing a rename.
And that is it for the shopping cart, in my next post, I’ll discuss the ShoppingCartController which uses this class.