One Size Does Not Fit All
As argued in a nice blog post by Richard Dingwall a one size fits all approach along the lines of:
is not going to work out well: Some of the methods in the one-size-fits-all interface might be inappropriate for some domain classes. So what do we do? -We introduce a number of interfaces each representing specific traits of a repository.
Traits
As proposed by Mr. Dingwall we create a number of interface each specifying one specific trait that a repository might have. Like these:
and probably others. Now that enables us to pick and choose which traits a specific repository should have. For instance a repository for the pet shops category objects would probably need all the traits listed above, hence it implements all those interfaces:
Incidentally implementing each of these traits is made really easy by Castle.ActiveRecord. All the smae I still don't want to implement them in each and every of the concrete repositories where they are used. This is where extension methods comes in.
Mixins
To be able to provide reusable implementations of the traits I implement an extension method for each method on the trait interfaces:
public static class ActiveRecordRepositoryImpl
{
internal static IEnumerable<Entity> GetAllImpl<Entity>(this ICanGetAll<Entity> self)
where Entity : class
{
return ActiveRecordMediator<Entity>.FindAll();
}
internal static Entity GetByIdImpl<Entity, Key>(this ICanGetById<Entity, Key> self, Key id)
where Entity : class
{
return ActiveRecordMediator<Entity>.FindByPrimaryKey(id);
}
internal static void RemoveImpl<Entity>(this ICanRemove<Entity> self, Entity entity)
where Entity : class
{
ActiveRecordMediator<Entity>.Delete(entity);
}
internal static void SaveImpl<Entity>(this ICanSave<Entity> self, Entity entity)
where Entity : class
{
ActiveRecordMediator<Entity>.Save(entity);
}
internal static int GetCountImpl<Entity>(this ICanGetCount<Entity> self)
where Entity : class
{
return ActiveRecordMediator<Entity>.Count();
}
}
Notice how easy Castle.ActiveRecord makes this. That's nice.
Right; then these *Impl extension methods are used in the concrete repositories:
public class CategoryRepository :
ICanGetAll<Category>,
ICanGetById<Category, int>,
ICanGetCount<Category>,
ICanRemove<Category>,
ICanSave<Category>
{
public IEnumerable<Category> GetAll()
{
return this.GetAllImpl();
}
public Category GetById(int id)
{
return this.GetByIdImpl(id);
}
public int GetCount()
{
return this.GetCountImpl();
}
public void Remove(Category entity)
{
this.RemoveImpl(entity);
}
public void Save(Category entity)
{
this.SaveImpl(entity);
}
}
And that's it.
So there still the duplication of calling *Impl methods in each concrete implementation method, but the methods actually implementing the repository traits are shared. I think that's pretty nice.