The purpose behind the repository pattern is to provide a layer of abstraction between your domain and data layer. For smaller projects, this typically isn't needed. However, larger projects can really benefit from a broker that specifically handles the back and forth between the two layers. With repositories your domain objects aren't burdened with infrastructure details and can therefore better focus on domain-specific behavior.
Without A Repository
To build this up to our grand finale (a generic repository), let's look at how you might do things in a small project without a repository. The simplest example might look something like:
public class User
{
private int _id;
...
public void Delete()
{
Factory.Get<IDataStore>().DeleteUser(_id);
}
}
Of course, our User class will likely need a Save and various Find methods. Also, Delete won't always be so straightforward – for data integrity/historic reasons you might not actually delete the entity, but rather flag it as inactive and update it:
public void Delete()
{
_isActive = false;
Factory.Get<IDataStore>().Update(this);
}
With non-generic Repositories
The above approach will quickly result in a burdened User class - especially when you look at all the different ways you'll want to fetch entities. The next step is to create repositories per-entity. So you end up with something like:
public class UserRepository : IUserRepository
{
public void Delete(User user)
{
user.IsActive = false;
Factory.Get<IDataStore>().Update(user);
}
public User FindByCredentials(string username, password)
{
//todo some null checks, maybe encrypt the password
var user = Factory.Get<IDataStore>().FindUser(username, password)
return user ?? User.Null;
}
}
This is a pretty common approach (you don't have to look too far back to some of my own code samples to see it) and it works pretty well. The main problem with this code is that you end up with a lot of repetitive code and an equal number of repetitive tests. If only there was a way to significantly reduce the repitition...
Generic Repository
Not too long ago I blogged about Generics saying that they were ideal for situations where code was similar but types differed. I provided a few examples, including a quick overview of a generic repository. Lets expand on that a bit. The beauty of a generic repository is that it gives you a powerful, flexible and easy to test class that can satisfy the vast majority of your needs – especially when you combine it with other generic classes (we use a generic controller for our CRUD which interacts with our generic repository which hits a generic DAL which is pretty much a wrapper to NHibernate (which itself is a generic implementation)).
First thing first, we start with some basic stuff:
public class Repository<T> where T : IEntity
{
private T _entity;
public Repository(T entity)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
_entity = entity;
}
}
The only note-worthy thing is the generic constraint stating that T must be of type IEntity. This is a custom type, which I simply define as:
public interface IEntity
{
int Id{ get; }
}
This not only lets us get an entity's Id (which is pretty important for some simple operations), but also makes sure someone doesn't create a repository for a type we don't control, such as:
new Repository<int>(3); //doh!
Next we can start implementing simple versions of our most common methods:
public virtual void Delete()
{
DataFactory.For<T>().Delete(_entity);
}
public virtual void Save()
{
DataFactory.For<T>().Save(_entity);
}
The real power though comes from being able to re-use more complex patterns. In our current system we have versioned entities (entities that can't be modified or deleted in order to preserver historical data integrity). Such entities are marked with the IVersioned interface:
public interface IVersioned<T> where T : IEntity
{
void MarkAsNew();
void MarkAsOld();
DateTime Created{get;set;}
T Root{get;set;}
}
Essentially, a versioned entity can be marked as active/inactive, always has a created property and always references its root element (this makes reporting across a single entity transparent from the versioning aspect). By implementing IVersioned<T> within an entity, such as:
public class User : IEntity, IVersioned<User>
{
private int _id;
private bool _isActive;
public int Id{get { return _id; }}
public bool IsActive{get { return _isActive; }}
public User Root{get;set;}
public DateTime Created{get;set;}
public void MarkAsOld()
{
_isActive = false;
}
public void MarkAsNew()
{
_created = DateTime.Now;
_isActive = true;
_id = 0;
}
}
We can now start wirting some really meaningful and reusable code:
public virtual void Delete()
{
if (!(_entity is IVersioned<T>))
{
DataFactory.For<T>().Delete(entity);
return;
}
var entity = (IVersioned<T>)_entity;
entity.MarkAsInactive();
DataFactory.For<T>().Save(entity);
}
public virtual void Save()
{
AssertValidity(); //validates _entity
var store = DataFactory.For<T>();
if (_entity.Id == 0 || !(_entity is IVersioned<T>))
{
store.Save(_entity);
return;
}
var original = store.Get(_entity.Id);
var entity = (IVersioned<T>)_entity;
entity.MarkAsNew();
entity.Root = original.Root ?? original;
original.MarkAsOld();
store.Save(entity, original)
}
This is just a narrow slice of what you can accomplish – with just a few interfaces you can accomplish a lot of work – we currently have IImmutable (can't be updated/deleted), IDefautable (an entity with IsDefault == true cannot be deleted and cannot be removed as the default), and IUnique which lists some unique constraint that the generic repository can enforce (a unique username for example).
What Is DataFactory<T>()...
Except for references to a data-layer, I left out all details about the underlying DAL that our repository has to interact with. Depending on what you're currently using, you might not see how the two will play nicely together (for example, I'm under the impression that my above code won't work all that well with a LINQ-to-SQL implementation). This is just one of the many reasons why you really should look at your DAL from the domain point of view (instead of the data perspective). If you aren't familiar with a quality O/R mapper, what are you waiting for?
What about Fetching?
I also conveniently left-out how to retrieve records. We can consider this a topic for future posts. However, the same concept applies. There are actually a number of tools at your disposal, including LINQ-to-Nhibernate, NHIbernate detached queries and generic object query. We currently use the latter, though we're looking into going more of an expression route. Here's a little sample:
//I dislike the magic string 'IsActive'
var query = new ModelQuery<User>()
.Where(w => w.AddCriteria("IsActive", Operation.Equals, true).Paged(1, 10);
var result = new Repository<User>().Find(query);
int pages = result.TotalPages;
var users = result.Entities;
Special Cases
Your generic repository won't be able to handle all cases. In such situations, you simply sub-class it and provide the custom logic – overriding existing methods or implementing your own.
Conclusion
I see more and more applications making use of numerous XXXRepository classes, and wonder whether they get tired, like I did, of copy-n-pasting the same base code and tests. Over the last year, I've learnt that if you copy existing tests as a starting point, there's a good chance you can introduce a generic implementation – which almost always results in cleaner and more testable code. Hopefully you also see how generics and interfaces can play off of each other – almost in a symbolic manner. By simply marking your entity with IDefautable (and implementing the sole IsDefault property) all of the necessary infrastructure and tests are already in place to support your systems concept of a default entity
.