I am what you might call... a lazy developer. But I also used to have a bad case of "not invented here syndrome". Anyways... I'm always working on improving my default architecture that I use in the line of applications I develop. I recently found Linq to NHibernate and a light went off in my head regarding this, the repository pattern, and unit testing.
In previous (successful) apps, I've done unit testing with a local instance of SQL Server or sometimes SQLite. Now all the big TDD people always say mock database access (e.g. using a repository), but I found that testing with local MSDE/SQL Express has been fine. Anyways... I wanted to improve my skills and "forget about the database" so that I can focus on the domain model like all good agilers do. So where am I headed with this...?
Repository pattern - so we want to mock the database by using an in memory counterpart, but at the same time, I know in the back of my head that I will be using a database in the end, so I dont' want to stray too far from what I will be implementing using NHibernate...
All repositories are going to have three responsibilities - Saving, Searching, and Deleting. Saving and Deleting are easy to mock. Let's for the sake of argument define a repository interface
interface IRepository
{
void Save(Entity e);
void Delete(Entity e);
T Get<T>(int id) where T : Entity;
?? Find(??);
}
In our in-memory repository, Save, Delete, and Get are very easy to implement. Save will add the entity to some internal list of entities and Delete will remove that entity. Get will do a search based on the T type parameter and the id to return the correct entity or null.
Our NHibernate implementation will be equally easy and will simply delegate most of the work to the ISession interface.
The real question comes with searching... how do we implement Find to be generic... if we use HQL we have to create a parser for our in memory implementation but the NHibernate implementation will be easy. If we use the NHibernate criteria API, we still have to implement a lot of work on the in memory side. If we use query objects we have a lot of work to do. Each query will need to be interpreted by the correct repository implementation. This is where LINQ comes in and Linq-to-NHibernate/Linq-to-objects...
Let's refine our IRepository interface to this
interface IRepository
{
void Save(Entity e);
void Delete(Entity e);
T Get<T>(int id) where T : Entity;
IQueryable<T> Query<T>();
}
Our in memory representation is going to simply generate an IEnumerable<T> representing the objects of the type parameter T and use the extension method AsQueryable to return the actual IQueryable interface
Our NHibernate implementation will use Linq-to-NHibernate and the extension method on our ISession provided to return the same IQueryable<T>
What is great about this is now we can create a set fo DAOs that will operate on the IQueryable using the Expression tree as our "query object".
So for example, let's say we want to get a user matching a specific email address, we can create a class called UserDao for example
class UserDao
{
User GetByEmailAddress(string emailAddress) { return repository.Query<User>().Where(u => u.EmailAddress == emailAddress).FirstOrDefault(); }
}
Now, I don't like creating a Dao for every type of Entity so instead, what I have done is created an extension class that provides any Finder methods that our repository will need...
public static class Dao
{
public static User GetByEmailAddress(this IQueryable<User> users, string emailAddress)
{
return users.Where(u => u.EmailAddress == emailAddress).FirstOrDefault();
}
}
And now I can use this code somewhere
User user = repository.Query<User>().GetByEmailAddress("patrick.greene@evot.net");
And it will work with either in memory OR NHibernate implementations. All we have to do is make a small NHibernate repository implementation. We also use a config setting in our App.config to specify whether we use in memory or NHibernate repository. Then we can do an integration test to make sure all our mapping files and db are correct simply by changing the config file for our unit tests to use NHibernateRepository as opposed to InMemoryRepository. All the tests which ran nicely on our in memory should run with NHibernate when our mapping files are correct.
Overall it's a pretty kick ass approach for TDD