Porting the MVC Music Store to RavenShoppingCart
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.
More posts in "Porting the MVC Music Store to Raven" series:
- (27 May 2010) ShoppingCartController
- (26 May 2010) ShoppingCart
Comments
That confused me for a second there, I think you meant "on the right you can see Raven's code" :)
Rob,
The image on the right isn't from Raven. Or the port.
It is part of the original codebase.
I think he meant this: "The method on the left is the original code, and on the left you can see Raven’s code"
Can you do a performance analysis of the original mvc music store vs the raven version?
" This isn’t that much of a problem, except that this is the wrong level to deal with persistence issues."
It's only wrong if it causes problems.
Des
Thanks, fixed
Lee,
It would be pretty pointless. There really isn't enough there to make a perf test. Not enough data, not enough use cases.
I'd be interested to see what your unit tests look like for this code. Please consider sharing them, too.
Rik,
There weren't any tests for the original code, so there aren't any for this.
They would be pretty simple.
Raven contains a testing mode, and you just call it.
The implementation of Total should also account for the quantity of the items.
public decimal Total
{
get { return Lines.Sum(t => t.Price * t.Quantity);
}
As Chaviv pointed out, the implementation of Total may not be correct.
Just because tests 'would be pretty simple' doesn't mean they're not important.
If I want to assign a auto generated id to shop cart, what should I do? How can I get the new shopcart id when migrate?
Thanks!
AntiGameZ,
Raven will use the hilo algorithm on the client, but there are additional strategies available for you
www.ravendb.net/.../docs-api-key-generation
Do can I find the code for ported MVC Music Store to Raven?
BJT,
It comes with Raven, look at the samples/ directory
Do you have to make the ShoppingCart.Lines property settable for some reason?
Comment preview