11

I have some difficulties implementing the repository and service pattern in my RavenDB project. The major concern is how my repository interface should look like because in RavenDB I use a couple of indexes for my queries.

Let's say I need to fetch all items where the parentid equals 1. One way is to use the IQueryable List() and get all documents and then add a where clause to select the items where the parentid equals 1. This seems like a bad idea because I can't use any index features in RavenDB. So the other approach is to have something like this, IEnumerable Find(string index, Func predicate) in the repository but that also seems like a bad idea because it's not generic enough and requires that I implement this method for if I would change from RavenDB to a common sql server.

So how can I implement a generic repository but still get the benefits of indexes in RavenDB?

Community
  • 1
  • 1
marcus
  • 9,616
  • 9
  • 58
  • 108

4 Answers4

10

First off, ask why you want to use the repository pattern?

If you're wanting to use the pattern because you're doing domain driven design, then as another of these answers points out, you need to re-think the intent of your query, and talk about it in terms of your domain - and you can start to model things around this.

In that case, specifications are probably your friend and you should look into them.


HOWEVER, let's look at a single part of your question momentarily before continuing with my answer:

seems like a bad idea because it's not generic enough and requires that I implement this method for if I would change from RavenDB to a common sql server.

You're going about it the wrong way - trying to make your system entirely persistence-agnostic at this level is asking for trouble - if you try hiding the unique features of your datastore from the queries themselves then why bother using RavenDB?

A method I tend to use in simple document-oriented (IE, I do talk in terms of data, which is what you appear to be doing), is to split up my queries from my commands.

Ask yourself, why do you want to query for your documents by parent ID? Is it to display a list on a page? Why are you trying to model this in terms of documents then? Why not model this in terms of a view model and use the most effective method of retrieving this data from RavenDB? (A query over an index (dynamic or otherwise)), stick this in a factory which takes 'some inputs' and generates 'the output' and if you do decide to change your persistence store, you can change these factories. (I go one step further in my ASP.NET MVC applications, and have single action controllers, and I don't call them controllers, making the query from those in most cases).

If you want to actually pull out your documents by parent id in order to update them or run some business logic across them, perhaps you've modelled them wrong - a write operation will typically only involve change to a single document, or in other words you should be modelling your documents around your transaction boundaries.

TL;DR

Think about what it is you actually want to achieve - why do you want to use the "Repository pattern" or the "Service pattern" - these words exist as ways of describing a scenario you might end up with if you model your application around your needs, as a common way of expressing the role of a certain object- not as something you need to shoehorn your every piece of functionality into.

metdos
  • 13,411
  • 17
  • 77
  • 120
  • @rob-ashton the reason I looked into the repository pattern is because I would like a simple layer of abstraction that prevents me from using the raven API directly, it may not be right thing to do that's why I'm asking. The application I'm developing is a pretty lightweight CMS, so I need to build up the navigation structure which implies these kinds of queries. Beyond the queries necessary to build up the hierarchical navigation I intend to modify a single document at the time. Again, maybe you are correct and I have modeled them wrong. – marcus Mar 23 '11 at 20:20
  • 1
    Consider the possibility of using your repository for writes (load entity, save entity, add entity), and creating class per query. You shouldn't be trying to avoid using the RavenDB API when composing the queries, it's built to be usable on purpose. – metdos Mar 24 '11 at 07:54
10

This post sums it all up very nicely:

http://novuscraft.com/blog/ravendb-and-the-repository-pattern

synhershko
  • 4,472
  • 1
  • 30
  • 37
8

Let's say I need to fetch all items where the parentid equals 1.

First, stop thinking of your data access needs this way.

You DO NOT need to "fetch all items where the parentid equals 1". It will help to try and stop thinking in such a data oriented way.

What you need is to fetch all items with a particular parent. This is a concept that exists in your problem space (your application's domain).

The fact that you model this in the database with a foreign key and a field named parentid is an implementation detail. Encapsulate this, do not leak it throughout your application.

One way is to use the IQueryable List() and get all documents and then add a where clause to select the items where the parentid equals 1. This seems like a bad idea because I can't use any index features in RavenDB. So the other approach is to have something like this, IEnumerable Find(string index, Func predicate) in the repository but that also seems like a bad idea because

Both of these are bad ideas. What you are suggesting is requiring the code that calls your repository or query to have knowledge of your schema.

Why should the consumer of your repository care or know that there is a parentid field? If this changes, if the definition of some particular concept in your problem space changes, how many places in your code will have to change?

Every single place that fetches items with a particular parent.

This is bad, it is the antithesis of encapsulation.

My opinion is that you will want to model queries as explicit concepts, and not lambda's or strings passed around and used all over.

You can model queries explicitly with the Specification pattern, named query methods on a repository, Query Object pattern, etc.

it's not generic enough and requires that I implement this method for if I would change from RavenDB to a common sql server.

Well, that Func is too generic. Again, think about what your consuming code will need to know in order to use such a method of querying, you will be tying upper layers of your code directly to your DB schema doing this.

Also, if you change from one storage engine to another, you cannot avoid re-implementing queries where performance was enough of a factor to use storage-engine-specific aids (indexes in Raven, for example).

quentin-starin
  • 26,121
  • 7
  • 68
  • 86
5

I would actually discourage you from using the repository pattern. In most cases, it is over-architecting and actually makes the code more complicated.

Ayende has made a number of posts to that end recently:

I recommend just writing against Raven's native API.

If you feel that my response is too general, list some of the benefits you hope to gain from using another layer of abstraction and we can continue the discussion.

Christopher Bennage
  • 2,578
  • 2
  • 22
  • 32
  • "I recommend just writing against Raven's native API." Don't know that I'd recommend that, but perhaps a more appropriate abstraction for the scenario. What Ayende seems to be getting at is that repository may be applicable to scenarios where modifications are being made, but not so much to scenarios where data is simply being read. – quentin-starin Mar 23 '11 at 00:53
  • @qes - why would you recommend against it? What are the potential problems that you are concerned about? – Christopher Bennage Mar 23 '11 at 01:08
  • I posted an answer that goes into why. If the code that would normally consume a repository consumes Raven API directly, you are forgoing any sort of encapsulation of the concepts specific to your problem space. Any time I've seen this done it inevitably leads to a ball of mud. How many places will the same query be written? And if it's written directly against a storage API - whether that's SQL, LINQ, or whatever - there will be duplication, and not a good kind. imho – quentin-starin Mar 23 '11 at 01:11
  • A simple single layer of abstraction that prevents raven's API to leak all throughout your application can't be bad! How he encapsulates is another question. And I am afraid I don't have the answer...yet :) – basarat Mar 23 '11 at 08:35
  • 2
    There is no one answer, that's the answer. – metdos Mar 23 '11 at 09:55
  • I'm not looking for best practice because we all have different requirements on the end result, what i'm looking for is recommendation. @Rob-Ashton, without having examined your code in raven gallery it seems like you are using the repository pattern, why did you choose this pattern over the more simple "I do all my raven queries in my view models" approach? – marcus Mar 23 '11 at 20:38
  • I use it for writes, you'll see the queries are in view factories as described – metdos Mar 24 '11 at 08:02
  • Repositories were used because I was showing how I'd use them, my other Raven projects just use session directly – metdos Mar 24 '11 at 08:05
  • I like Rob's answer. He expressed what I was trying to say. It's all about trade-offs and understanding the pros and cons of each pattern. You can look at the approach I'm taking with Studio here: https://github.com/bennage/ravendb/tree/master/Raven.Studio – Christopher Bennage Mar 24 '11 at 15:45
  • +1 qes. You don't necessarily need to use the repository pattern per se, but you absolutely DO NOT want to use the Raven API directly in your client code. – cdaq Apr 02 '13 at 14:44
  • 6
    Maybe a bit OT, but I've found that many of Ayende's blogs offer simply terrible advice. His recommendation to forgo a data abstraction layer in favor of calling ORM methods directly from client code would be laughable if there weren't so many developers patting him on the back in agreement. I wonder how his rationale of YAGNI is treating all of the developers who would like to switch to his RavenDB but can't because their apps are now tightly coupled to NHiberbate... – cdaq Apr 02 '13 at 15:15