views:

899

answers:

6

I've been exploring BDD/DDD and as a consequence trying to come up with a proper implementation of the Repository pattern. So far, it's been hard to find a consensus over the best way to implement this. I've tried to boil it down to the following variations, but I'm unsure which is the best approach.

For reference I'm building an ASP.MVC application with NHibernate as a back-end.

public interface IRepository<T> {
        // 1) Thin facade over LINQ
        T GetById(int id);
        void Add(T entity);
        void Update(T entity);
        void Remove(T entity);
        IQueryable<T> Find();
        // or possibly even
        T Get(Expression<Func<T, bool>> query);
        List<T> Find(Expression<Func<T, bool>> query);
}

public interface IRepository<T> {
        // 2) Custom methods for each query
        T GetById(int id);
        void Add(T entity);
        void Update(T entity);
        void Remove(T entity);
        IList<T> FindAll();
        IList<T> FindBySku(string sku);
        IList<T> FindByName(string name);
        IList<T> FindByPrice(decimal price);
        // ... and so on
}

public interface IRepository<T> {
        // 3) Wrap NHibernate Criteria in Spec pattern
        void Add(T entity);
        void Update(T entity);
        void Remove(T entity);
        IList<T> FindAll();
        IList<T> FindBySpec(ISpecification<T> specification);
        T GetById(int id);
}


public interface IRepository<T> {
        // 4) Expose NHibernate Criteria directly
        T GetById(int id);
        void Add(T entity);
        void Update(T entity);
        void Remove(T entity);
        IList<T> FindAll();
        IList<T> Find(ICriteria criteria);
        // .. or possibly
        IList<T> Find(HQL stuff);
}

My initial thoughts are that

1) is great from an efficiency point of view, but I may get into trouble as things get more complicated.

2) seems very tedious and could end up with a very crowded class, but otherwise offers a high degree of separation between my domain logic and data layer which I like.

3) seems difficult up front and more work to write queries, but limits cross contamination to just the Specs layer.

4) My least favorite, but possibly most direct implementation and possibly most database efficient for complex queries, though it puts a lot of responsibility on the calling code.

+4  A: 

I think they are all good options (except maybe 4 if you don't want to tie yourself to nhibernate), and you seem to have pros and cons well analyzed to make a decision on your own based on your current endeavor. Don't beat yourself too hard on this.

I'm currently working on a mixture between 2 and 3 I guess:

public interface IRepository<T> 
{
        ...
        IList<T> FindAll();
        IList<T> FindBySpec(ISpecification<T> specification);
        T GetById(int id);
}

public interface ISpecificRepository : IRepository<Specific> 
{
        ...
        IList<Specific> FindBySku(string sku);
        IList<Specific> FindByName(string name);
        IList<Specific> FindByPrice(decimal price);
}

And there's also a Repository (of T) base class.

Fredy Treboux
I suspect a little bit of #2 will creep into every solution.
cadmium
A: 

I'm a bif fan of 1 because I can create filters and paging extension methods than I can apply to the IQueryable<> return values for the Find method. I keep the extension methods in the data layer and then construct on the fly in the business layer. (Not totally pure, admittedly.)

Of course, when the system stabilises I have the option to make specific Find methods using the same extension methods and optimise using Func<>.

objektivs
+1  A: 

One of the things we are doing is that all of our repositories have different needs so we are creating a collection of interfaces:

public interface IReadOnlyRepository<T,V>
{
   V Find(T);
}

In this example the read only repository just does gets from the database. The reason for the T,V is the V represents what is returned by the repository and the T represents what is passed in, so you could do something like this:

public class CustomerRepository:IReadOnlyRepository<int, Customer>, IReadOnlyRepository<string, Customer>
{
    public Customer Find(int customerId)
    {
    }

    public Customer Find(string customerName)
    {
    }
}

I can also create seperate interfaces for the Add, Update and the Delete. This way if my repository does not need the behavior then it just does not implement the interface.

Michael Mann
This is interesting. I need to think about the potential benefits of using IReadonlyRepository<T,V>, but at first glance, I kind of like it. Thanks for sharing.
Kevin Swiber
If you find this useful can you up-vote my answer?
Michael Mann
+1  A: 

When using NH with Linq your repository can be:

session.Linq<Entity>()

The specifications are things that deal with:

IQueryable<Entity>

You can facade it all away if you want, but that is a lot of mundane work to abstract an abstraction.

Simple is good. Yah, NH does databases, but it provides so many more patterns. Having other than the DAL depend on NH is far from a sin.

eyston
But using session directly in logic, while simple, would violate Inversion of Control and make it really hard to test, would it not?
cadmium
Depends on how you structure your code. If you use the Unit of Work pattern, then ISession is your UoW. You can put a thin facade over ISession if that makes things better (I don't). So you then you are left with repositories. I view repositories as very basic -- get object, save object, expose objects as a collection (ISession does all of this). Queries/Specs can be handled by IQueryable. If you have domain logic that ends up depending on ISession it would have instead ended up depending on a Repository. Both of these are equally bad situations.
eyston
So ISession can end up being a bit of a 'do everything' kind of interface. That is why putting it behind another interface can be a bit more explicit on how it is being used (IUnitOfWork, IRepository<T>, etc). I think that part is personal preference, and I wouldn't spend a lot of time on engineering the perfect abstraction.
eyston
A: 

Wither the Repository

A relevant article by Jimmy Bogard of LosTechies

http://www.lostechies.com/blogs/jimmy%5Fbogard/archive/2009/09/10/wither-the-repository.aspx

Also, another quick article with some comments suggesting version #2 is really a DOA pattern and not a repository.

http://fabiomaulo.blogspot.com/2009/06/linq-and-repository.html

cadmium
+5  A: 

There's also a good argument for a "none of the above" approach.

The problem with generic repositories is that you're making the assumption that all objects in your system will support all four CRUD operations: Create, Read, Update, Delete. But in complex systems, you'll likely have objects that support only a few of the operations. For instance, you might have objects that are read-only, or objects that are created but never updated.

You could break the IRepository interface into small interfaces, for Read, Delete, etc. but that gets messy pretty quickly.

Gregory Young makes a good argument (from a DDD / software layering perspective) that each repository ought to support only the operations that are specific to the domain object or aggregate you're working with. Here's his article on generic repositories.

And for an alternate view, see this Ayende blog post.

dthrasher