Search This Blog

2009-08-27

DynamicProxy: An Elegant Solution for Session/Transaction/Exception Management in NHibernate (or any other ORM)

Session management is a well solved problem for web applications and many detailed solutions can be found in the internet. The same is not true for winforms applications. Although there are solutions available in the internet, many of them are theoretical or just “complicated” for the medium programmer. Besides that it was difficult to find a solution (I have never found one) that could work for both web and winforms applications.

After a while (days), it came up to me the idea of using service proxies with Castle Dynamic Proxies. It turned out to be the easiest and cleanest approach I could think of because it has the ability to inject (aspects) behaviour around the service methods.

The idea can be coded in the following way:
  • Service classes with standard namespace and virtual methods


namespace Sample.Service
{
  public class SystemLogRegistrationService
  {
    public virtual void Modify(long codLogSistema)
    {
      SystemLog systemLog = Repository.Get().Load(codLogSistema);            
      systemLog.SetMachine = "MAQUINA" + DateTime.Now;
      systemLog.SetUserName = "PESSOA" + DateTime.Now;            
      systemLog.SetSystemName = "SISTEMA" + DateTime.Now;
      Repository.Get().Save(systemLog);            
    }
  }
}


Do not get distracted with the service code. The important thing to notice above is that the service does not contain anything else other than processing the domain classes (in this case, SystemLog). Also note that all service methods must be virtual. Without that, dynamic proxy won't work for these methods. The details of Repository implementation are out of the scope of this article and this subject is covered in enough details in several articles throughout the internet. (You can also send me a comment or email if you need information about that)

  • Usage Example


In order to make use of proxified services, one must create some kind of generator whose creation will be explained next. The ProxyGenerator below is a simple static class for didactic purposes that is responsible for dynamically generate proxies from a given type injecting the necessary aspects such as session/transaction management and exception handling or any other aspect you might think about.

SomeService serv = ProxyGenerator.InjectSessionTransactionExceptionAspects &lt SomeService &gt ();
serv.Modify(12048); // <= Modify method has session/transaction/exception management
  • Creating a proxy service factory
The proxy generator can be implemented using Castle Dynamic Proxy API.
using System;
using Castle.DynamicProxy;

namespace Sample.Persistence
{
  public static class ProxyGenerator 
  {
    private static ProxyGenerator _generator = new ProxyGenerator();        
    public static TService InjectSessionTransactionExceptionAspects &lt TService &gt ()
    {
      return (TService)_generator.CreateClassProxy(
        typeof(TService),
        new SessionTransactionExceptionAspect());    
    }
  }
}
  • An interceptor for the service class methods
using System;
using Castle.DynamicProxy;
using NHibernate;
using NHibernate.Context;

namespace Sample.Persistence
{
  /// 
  /// Intercepts service methods (must be virtual) and inject
  /// session / transaction and exception aspects
  /// 
  public class SessionTransactionExceptionAspect: IInterceptor
  {
    /// 
    /// Intercepts service methods and adds the following behaviors
    /// >>> Before executing a method:
    ///     * opens session
    ///     * begins transaction
    /// >>> After executing method:
    ///     * Commits transaction
    /// >>> In case there is exception
    ///     * Rollbacks transaction
    ///     * Handles exception
    /// >>> At the end
    ///     * Closes session
    /// 
    public object Intercept(IInvocation invocation, params object[] args)
    {
      object retorno = null;
      ITransaction tx = null;
      try
      {          
        CurrentSessionContext.Bind(SessionFactory.Instance.OpenSession());
        tx = SessionFactory.Instance.GetCurrentSession().BeginTransaction();
        retorno = invocation.Proceed(args);
        tx.Commit();
      }
      catch (Exception exception)
      {
        if (tx != null) { tx.Rollback(); }
          throw exception;
      }
      finally
      {
        ISession s = SessionFactory.Instance.GetCurrentSession();
        s.Close();
        CurrentSessionContext.Unbind(s.SessionFactory);
      }
      return retorno;
    }
  }
}
Above is the center of the whole idea. The interceptor class above captures only the service methods and ignores the rest. The following tasks are executed inside a try-catch-finally: (when it is a service method)
  • Session is created
  • Transaction is initialized
  • The method itself is executed
  • if method is ok, transaction is confirmed
  • if there is exception, transaction is cancelled and exception is handled
  • Finally session is closed

7 comments:

Anonymous said...
This comment has been removed by a blog administrator.
Unknown said...

Why are you using the old and unsupported Dynamic Proxy version, instead of the current v2.1 ?

Trinition said...

Interesting idea. I've wanted to tie NHibernate's transactions into the System.TransactionModel. System.TransactionModel, I believe, has method attributes you can set. I wonder if there's a way to make simple method attributes just for NHibernate session management. Something like:

[NHibrnate.Transaction]
public Result SaveComment(...)

David Perfors said...

It is quiet nice for simple things like adding or updating one object to the database. but what if you have the need to modify a bunch of objects at the same time and you need to do that in one transaction... you need an seperate service for it? I don't think it very elegant to do so... But I must admit I don't have a perfect solution either....

OO Development said...

Triniton:
You can find this kind of transaction feature with Spring.NET .

OO Development said...

David:
In the example there was one domain object however I have been using this idea for several objects as you said. It is also possible to have internal services too.

Anonymous said...

Nice posting... http://www.itsolusenz.com