Intercepting Business Transactions

Posted on November 04, 2008 @ 03:02 csharp designpatterns oop

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.

1
2
3
4
5
6
7
8
  [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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  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...