We should probably talk about patterns for a bit. I like patterns, not because I think they're a design easy button, but because they're, essentially, just markers for how we implement things. Essentially a pattern is the meta-implementation that grabs the essense of what countless people have been implementing before that pattern emerged and boxed and labeled that body of knowledge.
Still, when I'm working on my own projects, I try to push myself to look at things differently, try new approaches to problems, or just play around to see what tastes right. My last post wasa good example of that. After trying things out, going back to Patterns of Enterprise Application Architecture, and thinking more about the feedback I received from Karl Seguin and Tuna Toksoz, I realized I was several miles down the wrong road.
With that, I'm going to step back a bit and introduce where I'm at now. Keep in mind, I'm not espousing these as best practices. Rather, this is an implementation that I'm trying out because I haven't (exactly) done it before but it feels right for my domain. My domain is a small one. When you're representing kennel listings, the services and features they offer, and reviews about those kennels, well let's just say the domain model fits nicely on an 8x11 sketch.
I also realized how truly wrong the vernacular is on a lot of blog posts. Like I said, I don't always go hunting for patterns, but when I use one, I want to make sure I'm calling it the right thing. The great thing about patterns is they (should) give us a universal language.
Enough rambling....
For my application, I assume that I'm going to keep my domain model loosely coupled from the services and persistence. Further, the persistence layer is going to be tightly coupled to a database (SQL Server in this instance). That is, I really don't need to support the ability to save to either xml or a database or flat file. But, if I do change in the future, I would just swap out the implementation behind the persistence concern. Next, I want a clean separation between each layer. I was going down a path where the front-end could just pass linq all the way back but, frankly, I don't need it.
Where my implementation might differ from a large app is that, on the large app, I might look at how I could easily encapsulate my domain queries as specifications or query objects (aside, I think 50% of the Specifiation implementations I've run across on-line should be called Query objects... again, if you're going to use a pattern then call it the right thing).
What I thought I wanted in a Repository was actually a DataMapper (see, I screwed it up myself... most of the Repository patterns I've seen, my own included, look a lot more like a DataMapper than a Repository).
Oh, yeah, looks like I started tying a bit too much again. Let's get to some code!
I'm going to cover one cross-section of the app, the User.
public interface IRepository<T> where T: IEntity
{
T Get(int id);
PagedList<T> List(Pager pager);
}
public interface IUserRepository : IRepository<User>, ISave<User>, IDelete<User>
{
User GetByLogin(string email, string password);
User GetByEmail(string email);
bool Exists(string email);
}
public interface ISave<T> where T : IEntity
{
void Save(T entity);
}
Two things to point out here. First, you can see where I'm specifying my queries inline in the user repository. I could have gone another way and just had a Find(Query q) and passed in a new FindUserByLogin query. But, again, that would be utter overkill for this. Second, rather than having an IPersistableRepository that implements IRepository (inheritance chaining), I have other interfaces (ISave and IDelete) to indicate allowed actions on that repository.
Now, on to the NHibernate-based implementation of the UserRepository. As an aside, when I'm going to consider something the core implementation, I prefer to just name it without a technology qualifying name (ie, it's UserRepository not NHibernateUserRepository)
public class UserRepository : PersistableRepository<User>, IUserRepository
{
public UserRepository(IDataMapper<User> mapper)
: base(mapper)
{
}
public virtual User GetByLogin(string email, string password)
{
return Mapper.FindOne(new ICriterion[] { Expression.Eq("Credentials.Email", email), Expression.Eq("Credentials.Password", password) });
}
public virtual User GetByEmail(string email)
{
return Mapper.FindOne(new ICriterion[] { Expression.Eq("Credentials.Email", email) });
}
public virtual bool Exists(string email)
{
return Mapper.Exists(new ICriterion[] { Expression.Eq("Credentials.Email", email) });
}
}
On the implementation side, I like to implement the specific case first and then move common functionality up to a shared base class as it emerges. So, in this case, I found that every entity I wanted to save I also wanted to be able to delete (contrast this with a domain where one might want to be able to save lots of different entities but some entities can never be deleted or delete means something different.
Anyway, for my entities, some commonality popped out (remember, these repositories are the implementation of interfaces and are very tied to NHibernate! The rest of the app, though, will talk to the interfaces so we can change as we want):
public abstract class Repository<T> : IRepository<T> where T : IEntity
{
private IDataMapper<T> _mapper;
protected IDataMapper<T> Mapper
{
get { return _mapper; }
}
public virtual T Get(int id)
{
return Mapper.Get(id);
}
public Repository(IDataMapper<T> mapper)
{
_mapper = mapper;
}
public virtual PagedList<T> List(Pager pager)
{
ISession session = Mapper.Session;
var criteria = session.CreateMultiCriteria()
.Add(session.CreateCriteria(typeof(T))
.SetFirstResult(pager.FirstRecord)
.SetMaxResults(pager.RecordsPerPage))
.Add(session.CreateCriteria(typeof(T))
.SetProjection(Projections.RowCount()));
var queryResults = criteria.List();
IList<T> data = new List<T>();
foreach (var o in (IList)queryResults[0])
data.Add((T)o);
var count = (int)((IList)queryResults[1])[0];
return new PagedList<T>(pager, data, count);
}
}
public abstract class PersistableRepository<T> : Repository<T>, IDelete<T>, ISave<T> where T : IEntity
{
public PersistableRepository(IDataMapper<T> mapper): base(mapper)
{
}
public virtual void Save(T entity)
{
Mapper.Save(entity);
}
public virtual void Delete(T entity)
{
Mapper.Delete(entity);
}
}
We haven't covered the DataMapper yet. We'll get there in a bit. Also, this is still early code. Once I get into things more, I'll probably refactor the paging so that I can pass in criteria and have it page the results of that. For now, I just want to be able to automatically page through the aggregate of any of my entities.
The DataMapper:
public interface IDataMapper<T> where T : IEntity
{
ISession Session { get; }
T Get(int id);
long Count(ICriterion[] criteria);
long Count();
bool Exists(ICriterion[] criteria);
void Save(T entity);
void Delete(T entity);
T FindOne(ICriterion[] criteria);
IList<T> FindAll(ICriterion[] criteria, params Order[] orders);
}
public class DataMapper<T> : IDataMapper<T> where T: IEntity
{
private readonly ISession _session;
public ISession Session
{
get { return _session; }
}
public DataMapper(ISession session)
{
_session = session;
}
public T Get(int id)
{
return _session.Get<T>(id, LockMode.None);
}
private void WithinTransaction(Action action)
{
ITransaction transaction = Session.BeginTransaction();
try
{
action();
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
finally
{
transaction.Dispose();
}
}
public void Save(T entity)
{
WithinTransaction(() => Session.SaveOrUpdate(entity));
}
public void Delete(T entity)
{
WithinTransaction(() => Session.Delete(entity));
}
public long Count(ICriterion[] criteria)
{
ICriteria crit = DataMapperHelper<T>.CreateCriteriaFromArray(Session, criteria, null);
crit.SetProjection(Projections.RowCountInt64());
object count = crit.UniqueResult();
return Convert.ToInt64(count);
}
public long Count()
{
return Count(null);
}
public bool Exists(ICriterion[] criteria)
{
return Count(criteria) > 0;
}
public IList<T> FindAll(ICriterion[] criteria, params Order[] orders)
{
ICriteria crit = DataMapperHelper<T>.CreateCriteriaFromArray(Session, criteria, orders);
return crit.List<T>();
}
public T FindOne(ICriterion[] criteria)
{
ICriteria crit = DataMapperHelper<T>.CreateCriteriaFromArray(Session, criteria, null);
return crit.UniqueResult<T>();
}
}
Note, the DataMapper is explicitly tied to the NHibernate Session. Also note, I've given up on the NHibernate.Linq implementation I had going on earlier. The library is probably actually there, especially for my needs, but it just felt that, for such a small problem space, I didn't need it.
Testing all of this is really painless (note, we'll get into the StructureMap configuration in another post):
public class UserRepositoryTest : BaseTest
{
[Fact]
public void Can_we_get_User()
{
ISession session = ObjectFactory.GetInstance<ISession>();
IDataMapper<User> mapper = new DataMapper<User>(session);
UserRepository repository = new UserRepository(mapper);
var user = repository.Get(1);
Assert.NotNull(user);
Assert.Equal(user.Id, 1);
}
[Fact]
[AutoRollback]
public void Can_we_save_User()
{
ISession session = ObjectFactory.GetInstance<ISession>();
IDataMapper<User> mapper = new DataMapper<User>(session);
UserRepository repository = new UserRepository(mapper);
User user = new User();
user.Name = "TestUser";
user.Credentials.Email = "test@user.com";
user.Credentials.Password = "password";
repository.Save(user);
Assert.True(user.Id > 0);
}
}
Anyway, there you have it, a cross-section view of where I landed for my persistence layer. I don't think there is a perfect answer to this problem, you have to take it with each new problem space. But, there will be several acceptable solutions that you can live with (forever or at least long enough to figure out where the design needs to go and refactor then). To that end, in my case, choosing to abandon the idea of passing Specification or Query objects around simplified things greatly. I don't have so many data needs that I'm in danger of bloating my repositories. Further, the code, at least in my infant understanding of the principles, seems to implement the SOLID principle fairly well. The Open/Close part of that is a bit questionable since it'd be much more composable if I were passing in query objects.
Later on I'll cover how I use StructureMap and Fluent NHibernate to breath some life into this model.
Oh, one last note, the DataMapperHelper code is a direct copy from one of NHibernate's Repository/UnitOfWork implementations.