Intercepting Business Transactions
In Patterns of Enterprise Application Architecture, the Unit of Work design pattern is defined as:
Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
NHibernate seems to have a great implementation of the unit of work, but understanding when to start and commit the unit of work without repeating yourself can be a little tricky. One thing we’ve been doing is starting a unit of work using an interceptor.
[Interceptor(typeof (IUnitOfWorkInterceptor))] public class AccountTasks : IAccountTasks { public bool are_valid(ICredentials credentials) { ... } }
Account tasks is a service layer piece, that is decorated with an interceptor that will begin and commit a unit of work.
public interface IUnitOfWorkInterceptor : IInterceptor {} public class UnitOfWorkInterceptor : IUnitOfWorkInterceptor { private readonly IUnitOfWorkFactory factory; public UnitOfWorkInterceptor(IUnitOfWorkFactory factory) { this.factory = factory; } public void Intercept(IInvocation invocation) { using (var unit_of_work = factory.create()) { invocation.Proceed(); unit_of_work.commit(); } } }
The interceptor starts a new unit of work, before proceeding with the invocation. If no exceptions are raised the unit of work is committed. If a unit of work is already started, the unit of work factory returns an empty unit of work. This ensures that if a service layer method calls into another method that it doesn’t start another unit of work.
public interface IUnitOfWorkFactory : IFactory<IUnitOfWork> {} public class UnitOfWorkFactory : IUnitOfWorkFactory { private readonly IApplicationContext context; private readonly IDatabaseSessionFactory factory; private readonly TypedKey<ISession> key; public UnitOfWorkFactory(IApplicationContext context, IDatabaseSessionFactory factory) { this.context = context; this.factory = factory; key = new TypedKey<ISession>(); } public IUnitOfWork Create() { if (unit_of_work_is_already_started()) { return new EmptyUnitOfWork(); } return create_a_unit_of_work().start(); } private bool unit_of_work_is_already_started() { return context.contains(key); } private IUnitOfWork create_a_unit_of_work() { var session = factory.create(); context.add(key, session); return new UnitOfWork(session, context); } }
The implementation of the repository pulls the active session from the application context.
public class DatabaseRepository<T> : IRepository<T> { private readonly IApplicationContext context; private readonly IKey<ISession> session_key; public DatabaseRepository(IApplicationContext context) { this.context = context; session_key = new TypedKey<ISession>(); } public IQueryable<T> all() { return the_current_session().Linq<T>(); } public void save(T item) { the_current_session().SaveOrUpdate(item); } public void delete(T item) { the_current_session().Delete(item); } private ISession the_current_session() { var current_session = context.get_value_for(session_key); if (null == current_session || !current_session.IsOpen) { throw new NHibernateSessionNotOpenException(); } return current_session; } }
For more information on Interceptors check out the Castle stack…