Implementing NHibernate Long Conversation with no HttpModules or AOP build time weaving

GIANT WARNING STICKER

This code is some major caveats to be taken when considered for production usage. If this would be used to solely power a shopping cart application it should work fine however if it will also govern the objects such as having administration functionality to update products etc it can lead to very inconsistent results due to users having the object in their first level cache (the ISession object) that you have modified and there seems to be no way to handle this correctly.

I have an open stack overflow question regarding this nature however there has been no positive feedback and even the direct NHibernate documents say “As the ISession is also the (mandatory) first-level cache and contains all loaded objects, we can propably use this strategy only for a few request/response cycles. This is indeed recommended, as the ISession will soon also have stale data.” So all in all it seems like a giant failure to attempt and after years worth of effort to solve this I have given up unless my SO thread is ever answered meaningful.

This post has been almost a year coming and it’s finally here. I still haven’t been posting much even being in the full swing of development, I just usually end up drained between work and overtime when I have years worth of development plans for my company that they would love to have all of them tomorrow. Fortunately my current employer is definitely is the best environment I’ve ever been in aside from that they give me all the tools I need to complete job they give me basically full reign on system architecture that I finally get to develop and put into production code I’ve worked off and on for over a year now. Also they have very realistic expectations that I’m not rushed to make poor decisions to push a product out the door that’s poorly designed. Now getting back on track.

It all started with Conversation Per Business Transaction using PostSharp and IoC written almost a year ago from today. This post at the time was definitely one of my largest successes in my development career however even though I solved the problem I wanted I still was not happy with the solution (probably because with PostSharp atleast back then my solution would take 5 minutes+ to build) still it was somewhat fragile for how I had to decorate classes with my [BusinessConversation] attributes that a nested method call would cause all hell to break loose.

To start I’m going to show you my final product, this is one of my model classes in the Model-View-Presenter software pattern

public class NotificationModel : INotificationModel
{
    private readonly INotificationProvider _NotificationProvider;

    public NotificationModel(INotificationProvider NotificationProvider)
    {
        _NotificationProvider = NotificationProvider;
    }

    [ConversationPersistance(ConversationCommitMode.Rollback)]
    public void AddButRollBack(NotifyRun notifyRun)
    {
        _NotificationProvider.Add(notifyRun);
    }

    public void Add(NotifyRun notifyRun)
    {
        _NotificationProvider.Add(notifyRun);
    }
}

As you can see on the AddAndRollBack method I do decorate the method with a custom attribute which does make my model somewhat aware of my implementation of my repository but I feel this is an acceptable pollution as it is reasonable to expect any method in the MVP pattern to know whether it’s role is to get data, save data or cancel an action. As you can see though the other method has no decoration and doesn’t require any decoration for the default action you configure.

Next I’m going to start with my inversion of control container, I use StructureMap however I believe this should also be able to be acheived using most other major DI frameworks.

private sealed class StructureMapRegistry : Registry
{
    public StructureMapRegistry()
    {
    //Configure default persisance mode
        ForRequestedType<ConversationCommitMode>().TheDefault.IsThis(ConversationCommitMode.Commit);

    //Castle DynamicProxy
        ForRequestedType<ProxyGenerator>().TheDefaultIsConcreteType<ProxyGenerator>()
      .CacheBy(InstanceScope.Singleton);

    //Castle DynamicProxy IInterceptor 
        ForRequestedType<ConversationInterceptor>().TheDefaultIsConcreteType<ConversationInterceptor>()
      .CacheBy(InstanceScope.Singleton);

     //Configuration wrapper of NH, this could be configured inline here but it's rather verbose
        ForRequestedType<INHibernateSessionManager>().TheDefaultIsConcreteType<NHibernateSessionManager>()
      .CacheBy(InstanceScope.Singleton);

    //This is a lightweight wrapper of the NH ISession that supports lazy access with the session
        ForRequestedType<IBusinessConversation>().TheDefaultIsConcreteType<LazyBusinessConversation>()
      .CacheBy(InstanceScope.HybridHttpSession);

    //This is my NH repository
        ForRequestedType<INotificationProvider>().TheDefaultIsConcreteType<NotificationProvider>()
      .CacheBy(InstanceScope.Hybrid);

    //This is a model class in the MVP pattern  that uses the above NH Provider
        ForRequestedType<INotificationModel>().TheDefaultIsConcreteType<NotificationModel>().CacheBy(InstanceScope.Hybrid)
            .EnrichWith((ctx, model) =>
      ctx.GetInstance<ProxyGenerator>().CreateInterfaceProxyWithTarget(model, ctx.GetInstance<ConversationInterceptor>()));

    //Set rules for what properties are injected on ObjectFactory.BuildUp(this)
        SetAllProperties(policy => policy.NameMatches(name => name.EndsWith("Provider") || name.EndsWith("Model")));
    }
}

I’m not going to explain my NotificationProvider class as it’s irrelevant to this post, it’s a class entirely derived from Creating a common generic and extensible NHiberate Repository, the SetAllProperties is just a StructureMap convention, ConversationCommitMode lets you inject your default configuration to the conversation, and the SessionManager also is unrelated to this post it’s just a wrapper class for boostrapping my NH session factory with Fluent NHibernate. The ProxyGenerator is a class in Castle DynamicProxy, I just use the version that’s already included with my NH. So now I’ll start with my Conversation class that has changed since my previous post. The biggest change is the client no longer needs to be aware of starting the session, connecting or disconnecting the session in anyway. The only need to care about what happens with the ISession after it is consumed.

public interface IBusinessConversation
{
    bool IsInTransaction { get; }
    bool IsNull { get; }
    ISession Session { get; }

    void Abort();
    void Commit();
    void CommitAndFlush();
    void End();
    void Rollback();
}

Part of the reason behind my BusinessConversation class is that I can implement the Null object pattern and lazy loading of the ISession itself so the session is delayed creation until one of my Repositories actually goes to use it and after it exists it’s only connected in instances that actually need it, not on every single page request! Onto the actual implementation.


public sealed class LazyBusinessConversation : IBusinessConversation
{
  private readonly INHibernateSessionManager _sessionManager;
  private ISession _session;

  public LazyBusinessConversation(INHibernateSessionManager sessionManager)
  {
    _sessionManager = sessionManager;
  }

  #region IBusinessConversation Members

  /// <summary>
  /// Rollback and do not commit the current transaction
  /// </summary>
  public void Rollback()
  {
    Rollback(_session);
  }

  /// <summary>
  /// Gets a value indicating whether this instance is in transaction.
  /// </summary>
  /// <value>
  ///   <c>true</c> if this instance is in transaction; otherwise, <c>false</c>.
  /// </value>
  public bool IsInTransaction
  {
    get { return _session.Is().Not.Null && _session.Is().InTransaction; }
  }

  /// <summary>
  /// Gets a value indicating whether this instance is null.
  /// </summary>
  /// <value><c>true</c> if this instance is null; otherwise, <c>false</c>.</value>
  public bool IsNull
  {
    get { return _session.Is().Null; }
  }

  /// <summary>
  /// Aborts this instance.
  /// </summary>
  public void Abort()
  {
    Abort(ref _session);
  }

  /// <summary>
  /// Commits the open transaction the session. Does not flush the session
  /// </summary>
  public void Commit()
  {
    Commit(_session, false);
  }

  /// <summary>
  /// Commits any open transaction in the session and
  /// flushes all pending changes to the database
  /// </summary>
  public void CommitAndFlush()
  {
    Commit(_session, true);
  }

  /// <summary>
  /// Ends this instance committing and flushing all data in session.
  /// </summary>
  public void End()
  {
    End(ref _session);
  }

  public ISession Session
  {
    get
    {
      if (!IsInTransaction)
      {
        Resume(_sessionManager, ref _session);
      }

      return _session;
    }
  }

  #endregion

  #region Class Methods

  private static void Abort(ref ISession session)
  {
    if (session.Is().NullOrClosed) return;

    Rollback(session);
    TerminateSession(ref session);
  }

  private static void Commit(ISession session, bool flush)
  {
    if (session == null)
      throw new ArgumentNullException("session");

    if (session.Is().InTransaction)
      session.Transaction.Commit();

    if (flush)
      session.Flush();

    session.Disconnect();
  }

  private static void End(ref ISession session)
  {
    if (session.Is().NullOrClosed) return;

    Reconnect(session);

    Commit(session, true);

    TerminateSession(ref session);
  }

  private static void Reconnect(ISession session)
  {
    if (session == null)
      throw new ArgumentNullException("session");

    if (session.IsConnected) return;

    session.Reconnect();
  }

  private static void Resume(INHibernateSessionManager sessionManager, ref ISession session)
  {
    if (session.Is().NullOrClosed)
      session = sessionManager.GetSession();

    if (session.Is().Null)
      throw new AccessViolationException("Session could not be created");

    Reconnect(session);

    if (session.Is().Not.InTransaction)
      session.Transaction.Begin();
  }

  private static void Rollback(ISession session)
  {
    if (session == null)
      throw new ArgumentNullException("session");

    if (session.Is().InTransaction)
      session.Transaction.Rollback();
  }

  private static void TerminateSession(ref ISession session)
  {
    session.Close();
    session.Dispose();

    /// Safety net incase conversation is reused
    /// this will guarantee a new session is created
    session = null;
  }

  #endregion
}

Sorry, long block of code I know, before I get into the specifics of it the majority of this class is pretty simple I inject my SessionFactory to the Conversation so I can new sessions when I need them. Other than that the majority of the methods do exactly what they are named and just complete the session or transaction in 1 way or another. Also I’m sure you noticed I call session.Is() this is an extension method that allows me to fluently check session state instead of having to do if(session != null && session.IsInTransaction) etc. Where all of the work really occurs is ISession { get }

IsInTransaction will always return false the first time .Session is accessed during a page request because either the session will be null or it will be disconnected. So the first time it’s accessed it calls Resume. Inside Resume I check to see if it exists, if not, new a session. If it already existed it needs to be reconnected. This handles all of the lazy loading (or lazy reconnection) this is a big advantage over pretty much every solution I’ve seen for the Long Conversation pattern is that they are based on instantiating a session for every user, connecting it at the start of every page request and committing it at the end of every page request regardless of whether or not they need the session at all! Also many of these patterns rely on HttpModules which makes testing so much harder!

At some point this class could probably use some refactoring to split out the methods that actually reattach the session, currently I have a few them with static methods and ref pointers so that you couldn’t call Resume(_sessionManager, Session) from inside the class easily and cause all kinds of unnecessary recursion. Of course as I type this I realize an explicitly defined Session { get } would go a long way for that, I also might consider wrapping my static methods into a class of their own and actually injecting that into the conversation. Now that you can see what my conversation class does we’ll take a look at what’s going on with

ForRequestedType<INotificationModel>().TheDefaultIsConcreteType<NotificationModel>().CacheBy(InstanceScope.Hybrid)
  .EnrichWith(
  (ctx, model) =>
  ctx.GetInstance<ProxyGenerator>().CreateInterfaceProxyWithTarget(model, ctx.GetInstance<ConversationInterceptor>()));

Per the StructureMap documentation

EnrichWith() — Registers a Func that runs against the new object after creation and gives you the option of returning a different object than the original object

So what happens here is you’re intercepting StructureMap giving you an instance of the NotificationModel and doing something with it. In this case what we’re going to do with it is use Castle DynamicProxy to actually create an entirely new NotificationModel that will wrap all of our methods with what we do in the ConversationInterceptor!

public class ConversationInterceptor : IInterceptor
{
  private readonly ConversationCommitMode _conversationCommitMode;

  public ConversationInterceptor(ConversationCommitMode conversationCommitMode)
  {
    _conversationCommitMode = conversationCommitMode;
  }

  private ConversationCommitMode GetConversationMode(ICustomAttributeProvider methodInfo)
  {
    var attribute = methodInfo.GetCustomAttributes(typeof (ConversationPersistanceAttribute), true);

    return attribute == null || attribute.Length == 0
           ? _conversationCommitMode
           : ((ConversationPersistanceAttribute) attribute[0]).ConversationMode;
  }

  #region IInterceptor Members

  public void Intercept(IInvocation invocation)
  {
    invocation.Proceed();

    var conversation = ObjectFactory.GetInstance<IBusinessConversation>();

    if (conversation.IsNull) return;

    switch (GetConversationMode(invocation.Method))
    {
      case ConversationCommitMode.CommitAndFlush:
        conversation.CommitAndFlush();
        break;
      case ConversationCommitMode.Rollback:
        conversation.Rollback();
        break;
      case ConversationCommitMode.Abort:
        conversation.Abort();
        break;
      case ConversationCommitMode.Commit:
      default:
        conversation.Commit();
        break;
    }
  }

  #endregion
}

So what happens after the ProxyGenerator creates our new class is instead of directly calling each method in the Model it will execute the code in Intercept(IInvocation) which for our case will first immediately run the method because we only need to do more AFTER it completes. Now as it runs the method it will call down to the Provider which will then use the lazyBusinessConversation.Sesssion property which will activate our session, run it’s NH commands and start to trace the call chain back after it finishes the execution of the original method call for any of the Model methods it returns back to the Intercept method.

The first thing we’ll check is if the session is null because if it is (meaning we didn’t touch it in our method) we don’t need to do anything else! I’ll explain why this will be very useful in a minute. Assuming we did an NH operation we’re going to check to see if I have our custom attribute added to the method like I do for AddAndRollBack in the model. If it’s there it will return that otherwise it will return the default commit operation that we injected into our IoC container. The switch statement is self explanatory. If we didn’t actually interact with NH on our round trip but the session was created before it still calls conversation.Commit() but looking above you can see it verifies it has an open transaction otherwise it will do nothing, this covers our lazy connection handling without having to embed that logic inside the interceptor.

Now the reason why I handle the null checking as some of you may have wondered, why would the session ever be null when the model is basically just a wrapper around the NH provider classes, the flexibility my solution offers is if you move the .EnrichWith() method in our StructureMap registry off of the Model classes you can put them on either your View or Presenter classes so that the session will be connected for basically the full page life cycle so you should get full support from lazy loading! Also since alot of methods in your views or presenters will have nothing to do talking to NH it will skip all of the work with NH on those requests! This is where I would Enrich my classes outside of my test project.

I would really like to thank Sam over on the StructureMap forums for his very insightful post that gave me the knowledge I needed to see how to solve the Long Transaction pattern using the castle dynamic proxy framework!

Right now none of this code is in my StructuredWeb SVN currently but I will merge all my current work back into SW soon!

kick it on DotNetKicks.com

Shout it

BloggingContext.ApplicationInstance.CompleteRequest();

About these ads

7 thoughts on “Implementing NHibernate Long Conversation with no HttpModules or AOP build time weaving

  1. Implementing NHibernate Long Conversation with no HttpModules or AOP build time weaving « dotNetChris @ Marisic.Net…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…

  2. It looks good, but I just wish you could have put a code sample up. Something that your readers could download and run. It could be quite overwhelming to someone who doesn’t understand structure map and the idea of a Unit of Work.

  3. oh one more, one more thing.

    [ConversationPersistance(ConversationCommitMode.Rollback)]

    This scenario makes no sense, when would you ever know that you need to rollback unless you had an error. This makes all the code in that function moot if the final goal is to commit to a database.

    • I agree with you it won’t be used often, the main usage of it is for testing so you can do a bunch of operations and then the transaction at the end is rolled back. In production usage the mode you would use to cancel pending changes would be ConversationCommitMode.Abort this would purge all unflushed changes in your ISession and the next NH action you did would create you a new ISession. This would be useful for something like EmptyShoppingCart, cancel on a wizard etc.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s