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 [Interceptor(typeof (IUnitOfWorkInterceptor))]
2 public class AccountTasks : IAccountTasks
3 {
4 public bool are_valid(ICredentials credentials)
5 {
6 ...
7 }
8 }
Account tasks is a service layer piece, that is decorated with an interceptor that will begin and commit a unit of work.
1 public interface IUnitOfWorkInterceptor : IInterceptor
2 {
3 }
4
5 public class UnitOfWorkInterceptor : IUnitOfWorkInterceptor
6 {
7 private readonly IUnitOfWorkFactory factory;
8
9 public UnitOfWorkInterceptor(IUnitOfWorkFactory factory)
10 {
11 this.factory = factory;
12 }
13
14 public void Intercept(IInvocation invocation)
15 {
16 using (var unit_of_work = factory.create()) {
17 invocation.Proceed();
18 unit_of_work.commit();
19 }
20 }
21 }
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 public interface IUnitOfWorkFactory : IFactory<IUnitOfWork>
2 {
3 }
4
5 public class UnitOfWorkFactory : IUnitOfWorkFactory
6 {
7 private readonly IApplicationContext context;
8 private readonly IDatabaseSessionFactory factory;
9 private readonly TypedKey<ISession> key;
10
11 public UnitOfWorkFactory(IApplicationContext context, IDatabaseSessionFactory factory)
12 {
13 this.context = context;
14 this.factory = factory;
15 key = new TypedKey<ISession>();
16 }
17
18 public IUnitOfWork create()
19 {
20 if (unit_of_work_is_already_started()) {
21 return new EmptyUnitOfWork();
22 }
23
24 return create_a_unit_of_work().start();
25 }
26
27 private bool unit_of_work_is_already_started()
28 {
29 return context.contains(key);
30 }
31
32 private IUnitOfWork create_a_unit_of_work()
33 {
34 var session = factory.create();
35 context.add(key, session);
36 return new UnitOfWork(session, context);
37 }
38 }
The implementation of the repository pulls the active session from the application context.
1 public class DatabaseRepository<T> : IRepository<T>
2 {
3 private readonly IApplicationContext context;
4 private readonly IKey<ISession> session_key;
5
6 public DatabaseRepository(IApplicationContext context)
7 {
8 this.context = context;
9 session_key = new TypedKey<ISession>();
10 }
11
12 public IQueryable<T> all()
13 {
14 return the_current_session().Linq<T>();
15 }
16
17 public void save(T item)
18 {
19 the_current_session().SaveOrUpdate(item);
20 }
21
22 public void delete(T item)
23 {
24 the_current_session().Delete(item);
25 }
26
27 private ISession the_current_session()
28 {
29 var current_session = context.get_value_for(session_key);
30 if (null == current_session || !current_session.IsOpen) {
31 throw new NHibernateSessionNotOpenException();
32 }
33 return current_session;
34 }
35 }
36