-
-
Save benfoster/6900744 to your computer and use it in GitHub Desktop.
public interface IEntityStore | |
{ | |
TEntity Get(Guid id) where TEntity : Entity | |
IQueryable<TEntity> Query() where TEntity : Entity | |
void Store<TEntity>(TEntity entity) where TEntity : Entity | |
void Delete<TEntity>(Guid id) where TEntity : Entity | |
} |
I have to agree. The point I'm getting to (and I plan on elaborating further on a blog post) is that the generic repository pattern produces code that is fragmented and not clear of its intentions.
I think this has a lot to do with exposing IQueryable
to the UI layer or even IEnumerable
and allowing predicates to be passed. I'm guilty of this on many projects and it doesn't take long to find cases where I'm duplicating the same criteria or ordering logic.
Like many developers I spend a lot of time working on my domain, modelling behaviour explicitly only to ignore the same rules when it comes to querying. I believe this is an area that CQRS helps a lot with (even at a simplified level) and I've had great success with it on a recent project.
The interface above is essentially what would be available to the components that interface with the domain. In fact, I'd be tempted to even remove the Query()
method since we're usually dealing with single ARs.
The "Query" part of CQRS would be something different entirely with queries modelled explicitly e.g.
IUserQueries.GetUserByEmail(string email)
I'd still likely have a small abstraction over an NHibernate session for testing purposes but I would no longer be passing predicates/criteria from my UI.
We're on the same page. I don;t know if you should feel guilty about queryable+predicates though - as long as you have a set of functional tests, you can refactor towards this approach with impunity if you feel the code is getting too messy.
I think its ok to create a ball of mud if you know you can polish it up later - one reason it's important to promote the approach here over the typical Repository.
(My preference for named delegates over the IUserQueries class is to avoid creating a class that ends up getting referenced all over the place, creating connections between namespaces. With standalone delegates you can keep them with the caller).
Warning, unsolicited opinions ahead!
The generic repository (which I think this is a form of) has long term maintenance issues where it isn't typesafe which Types can be used with the Query method - I'd recommend adding a lot of
where TEntity : IAggregateRoot
orwhere TEntity : IQueryable
conditions, making it explicit which types the interface can be used with.If you don't do that you can ditch the generic parameter on Store/Delete - in it's current state it doesn't add any function or typesafety to the code.
Although I am a massive IQueryable fan, I also reckon injecting delegates like
delegate User GetUserByEmail(string email)
backed by methods in the DAL go a long way to adding meaning in your domain model over
.Where(u => u.Email = x)