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…

Collecting Errors