Ayende @ Rahien

Refunds available at head office

Porting the MVC Music Store to Raven: ShoppingCart

image

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:

image

And as an entity in our application:

 image

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:

image

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.

image image

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.

image image

 

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.

imageimage

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:

image image

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:

image 

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.

Tags:

Posted By: Ayende Rahien

Published at

Originally posted at

Comments

Rob Ashton
05/26/2010 02:49 PM by
Rob Ashton

That confused me for a second there, I think you meant "on the right you can see Raven's code" :)

Ayende Rahien
05/26/2010 02:52 PM by
Ayende Rahien

Rob,

The image on the right isn't from Raven. Or the port.

It is part of the original codebase.

Des
05/26/2010 03:22 PM by
Des

I think he meant this: "The method on the left is the original code, and on the left you can see Raven’s code"

Lee
05/26/2010 03:34 PM by
Lee

Can you do a performance analysis of the original mvc music store vs the raven version?

gunteman
05/26/2010 10:02 PM by
gunteman

" 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.

Ayende Rahien
05/27/2010 02:36 AM by
Ayende Rahien

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.

Rik Hemsley
05/27/2010 08:19 AM by
Rik Hemsley

I'd be interested to see what your unit tests look like for this code. Please consider sharing them, too.

Ayende Rahien
05/27/2010 10:40 AM by
Ayende Rahien

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.

Chaviv
05/27/2010 11:38 AM by
Chaviv

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);

}

Rik Hemsley
05/27/2010 04:47 PM by
Rik Hemsley

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.

AntiGameZ
05/30/2010 02:48 PM by
AntiGameZ

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!

BJT
05/31/2010 08:48 PM by
BJT

Do can I find the code for ported MVC Music Store to Raven?

Ayende Rahien
05/31/2010 09:57 PM by
Ayende Rahien

BJT,

It comes with Raven, look at the samples/ directory

ulu
06/22/2010 09:43 AM by
ulu

Do you have to make the ShoppingCart.Lines property settable for some reason?

Comments have been closed on this topic.