Ayende @ Rahien

It's a girl

Porting MVC Music Store to Raven: Porting the HomeController, the map/reduce way

The current HomeController looks like this:

image

I really don’t like the fact that the controller issues queries like that, but we will let it go for now.

This query (thanks to EF Prof) looks like this:

image

And here we run into a very interesting problem, we can’t really replicate this query. The reason is that this query runs over multiple tables which our model says would be in different documents.

There are several ways in which we can fix this. One way of doing this would be to define a map / reduce index on top of orders.

Note: Yes, I am familiar with this comic.

The way that I am about to show you isn’t the way I would recommend going for real, but I want to show it anyway. I’ll discuss the idiomatic Raven way of handling this feature in my next post.

Map/reduce in Raven is just a couple of Linq queries, so it is nothing to be worried about. As a reminder, we have the following order documents in our database:

image_thumb9 image image

We define the index “SoldAlbums” using the following queries.

// map
from order in docs.Orders
from line in order.Lines
select new{ line.Album, line.Quantity }

// reduce
from result in results
group result by result.Album into g
select new{ Album = g.Key, Quantity = g.Sum(x=>x.Quantity) }

As you can see, those are two very simple Linq queries.

The result of which would be:

image

Once we have that, it is trivial to derive the answer to GetTopSellingAlbums. Indeed, the following function implements the exact same logic and has the same output as the previous implementation:

image

The way it work is pretty simple, we get the most sold albums (by sorting on descending quantity), then load them from the database. Because we might have less than count top selling albums, we need to top it off from regular albums.

This mean that this code execute 2 – 3 queries. I don’t really like it, but on my machine, it takes about less than 10 ms to do all three requests, which is livable.

The reason that I am posting this solution is that I want to show this as an approach to a problem, not as the recommended approach for how to solve it, I’ll do that in my next post.

Tags:

Posted By: Ayende Rahien

Published at

Originally posted at

Comments

Demis Bellot
05/21/2010 11:01 AM by
Demis Bellot

Interesting. Looking forward to seeing your next post on the 'recommended approach' to see if we come up with similar solutions.

Is there any reason why you're choosing to go with 'OrderBy("-Quantity")' rather than the more natural 'OrderByDescending("Quantity")' ?

Ayende Rahien
05/21/2010 11:03 AM by
Ayende Rahien

Demis,

This is using the low level API, the high level API would just use Linq.

Demis Bellot
05/21/2010 11:05 AM by
Demis Bellot

Very Nice, supporting multiple levels of API! And I thought I was the only dev crazy enough to spend the time to do this :)

Ayende Rahien
05/21/2010 11:13 AM by
Ayende Rahien

Demis,

You pretty much have to, because there is usually more power (but more work) at lower levels.

And yes higher levels offer better productivity

Chris
05/21/2010 05:09 PM by
Chris

I hope the idiomatic way is more immediately comprehensible and succinct. It sure does seem like a bunch more code for the same functionality at first glance.

Frank Quednau
05/22/2010 08:37 AM by
Frank Quednau

It is fantastic how you have managed to create a buzz around document DBs and especially RavenDB.

I am currently wondering how many good projects may be out there that go unattended and without the focus they may deserve because there isn't a 'famous' name attached to it and simply go under in the webs due to the sheer mass of signals.

The situation has grown even worse with less people blogging and more people tweeting, where the signal-to-noise ratio is, at least from my perspective, lower.

Ayende Rahien
05/22/2010 08:55 AM by
Ayende Rahien

Frank,

Take a look at how Rhino Mocks became famous.

Frank Quednau
05/22/2010 10:26 AM by
Frank Quednau

Ayende,

that was somewhat ahead of my time...how can I take a look? Your first posts are in 2005 and judging by the comments left you didn't have that many readers then than now...

I am genuinely interested to hear that story :)

Ayende Rahien
05/22/2010 10:32 AM by
Ayende Rahien

Frank,

Putting it out, making it work, listening to people, writing blog posts (I had about 80 subscribers then), writing articles in places like CodeProject, participating in mailing lists about TDD.

That sort of thing.

IOW, a lot of really hard work

Frank Quednau
05/22/2010 10:41 AM by
Frank Quednau

That's the thing isn't it? It isn't sufficient to be a Genius that found the end to all...it takes a lot of sweat for others to notice. It's probably a good thing, as it's honest stuff.

Ron the MVC Rookie
05/24/2010 04:31 PM by
Ron the MVC Rookie

I really don’t like the fact that the controller issues queries like that, but we will let it go for now.

What don't you like about it?

...we can’t really replicate this query. The reason is that this query runs over multiple tables which our model says would be in different documents.

Replicate as in rewrite for RavenDB? Sorry, I am lost.

Ron the MVC Rookie

Ayende Rahien
05/25/2010 07:05 PM by
Ayende Rahien

Ron,

The controller should orchestrate stuff, not make actual queries.

And replicate == re-write - in this instance

Ayende Rahien
05/26/2010 09:50 AM by
Ayende Rahien

Ron,

This is not my app. I just ported it, I didn't re-architect it.

Moreover, stuff that work for very small app isn't appropriate for bigger apps

Ron
05/27/2010 12:48 AM by
Ron

Thank you Ayende. Learning a lot of best practices form you!

Ron

Ayende Rahien
05/31/2010 08:53 AM by
Ayende Rahien

Örjan,

With the MVC Music Store, I only ported the code, I did NOT change the design in any significant way.

I would generally create Query objects for anything that was more complicated than a Load by id, yes.

Comments have been closed on this topic.