Conversation Per Business Transaction using PostSharp and IoC

Earlier this month I encountered the notion of the Conversation per Business transaction paradigm for NHibernate session management as the overrall successor to the session per request pattern. With my exploration on this topic I found it quite confusing to implement in the only existing examples of it. Which is why I wrote a simple version for myself leveraging inversion of control and PostSharp.

Firstly,  everything drives off an IConversation:

public interface IConversation
{
    void Start();
    void Pause();
    void Resume();
    void End();
    void Abort();
    ISession Session { get; }
}

The conversation is a fairly simple idea, just as in a real life conversation you have a start to it, you can pause the conversation (which is how to commit the conversations), resume the conversation, end the conversation and abort the conversation. Imagine the scenario of a school class and students who have learning commited to their memory. A day could follow a similar context as this:

8:00 Class starts
Math is taught, Science is taught
12:00 Class is paused for recess (all information is committed)
13:00 Class is resumed
Creationism is taught, but realized to be made up and Class is aborted
14:00 Class is started again
More subjects are taught
15:00 Class is ended and all information flushed to students long term memory.

This example is fairly contrived but if you replace students with the database and act of class being taught with the conversation it should be pretty clear a flow that could occur over a single session for a conversation.

Next is the actual implementation of the Conversation class.

public class Conversation : IConversation, IDisposable
{
    private readonly INHibernateSessionManager _sessionManager;

    public ISession Session { private set; get; }

    [InjectionConstructor]
    public Conversation(INHibernateSessionManager sessionManager)
    {
        _sessionManager = sessionManager;
    }

    /// <summary>
    /// Starts this instance.
    /// </summary>
    public void Start()
    {
        if (Session != null && (Session.IsOpen || Session.IsConnected))
        {
            Abort();
        }
        Session = _sessionManager.GetSession(FlushMode.Never);
        Resume();
    }

    /// <summary>
    /// Pauses this instance.
    /// </summary>
    public void Pause()
    {
        Commit(Session);

    }

    /// <summary>
    /// Resumes this instance.
    /// </summary>
    public void Resume()
    {
        if (Session == null)
            Start();

        if (Session == null)
            throw new AccessViolationException("Session could not be created");

        if (!Session.IsConnected)
        {
            Session.Reconnect();
        }
        Session.BeginTransaction();
    }

    /// <summary>
    /// Ends this instance.
    /// </summary>
    public void End()
    {
        if (Session == null || !Session.IsOpen) return;

        FlushAndCommit(Session);
        Session.Close();
    }

    /// <summary>
    /// Aborts this instance.
    /// </summary>
    public void Abort()
    {
        if (Session == null || !Session.IsOpen) return;

        if (Session.Transaction != null && Session.Transaction.IsActive)
        {
            Session.Transaction.Rollback();
        }
        Session.Close();
    }

    /// <summary>
    /// Commits the specified session.
    /// </summary>
    /// <param name="session">The session.</param>
    private static void Commit(ISession session)
    {
        if (session.Transaction != null && session.Transaction.IsActive)
        {
            session.Transaction.Commit();
        }
    }

    /// <summary>
    /// Flushes the and commit.
    /// </summary>
    /// <param name="session">The session.</param>
    private static void FlushAndCommit(ISession session)
    {
        if (session.Transaction == null || !session.Transaction.IsActive) return;

        session.Flush();
        session.Transaction.Commit();
    }

    #region Implementation of IDisposable

    public void Dispose()
    {
        End();
    }

    #endregion
}

This class is a little bit long but nothing is that complex and I think every method is very straight forward. I’m going to skip over a few things that are not needed for understanding what this class does. The INHibernateSessionManager is an interface to a class that is a basically a wrapper to NHibernate’s ISessionFactory. For all intensive purposes this class could be ISessionFactory instead and the implementation wouldn’t change other than it would be session = Factory.OpenSession(); session.FlushMode = FlushMode.Never;

The InjectionConstructor attribute is a Microsoft Unity (Inversion of Control framework) specific convention that informs Unity to inject the NHibernate SessionFactory dependency into a Conversation anytime it is created. Unity will store a singleton copy of the SessionFactory for the life of the application.

Next up is the DataProvider class that will actually use the Conversation to talk to the database physically.

public class EmployeeDataProvider : IEmployeeDataProvider
{
    private readonly IConversation _conversation;

    [InjectionConstructor]
    public EmployeeDataProvider(IConversation conversation)
    {
        _conversation = conversation;
    }

    IList<Employee> IEmployeeDataProvider.GetEmployees()
    {
        return _conversation.Session.CreateCriteria(typeof (Employee)).List<Employee>();
    }
}

This is a very basic method that is used with NHiberate, it’s the syntax to get a list of objects from the database. I have some other methods in my provider but the code is less important now onto calling the provider.

[Test]
[BusinessConversation]
public void BusinessConversationTest()
{
    const int id = 9;
    var employee = _provider.GetEmployee(id);
    Assert.IsNotNull(employee);

    var employees = _provider.GetEmployees();
    Assert.IsTrue(employees.Count > 0);

    var territories = _provider.GetTerritories();
    Assert.IsTrue(territories.Count > 0);
}

In the setup call for this test Unity will resolve the _provider and assign it a Conversation which has the SessionFactory in it for opening the session. The same session is used for each of the calls to the database and committed as a single transaction when the method exits. This is done automagically by PostSharp.

PostSharp is an aspect orientated programming framework that allows you to create attributes can trigger methods before entering a method with the attribute and after exiting. The code for the BusinesConversation attribute is

[Serializable]
public sealed class BusinessConversation : OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionEventArgs eventArgs)
    {
        var container = HttpContext.Current != null ?
            HttpContext.Current.Application.GetContainer() : UnityTestContainer.Instance;
        var conversation = container.Resolve<IConversation>();
        conversation.Resume();
    }

    public override void OnExit(MethodExecutionEventArgs eventArgs)
    {
        var container = HttpContext.Current != null ?
            HttpContext.Current.Application.GetContainer() : UnityTestContainer.Instance;
        var conversation = container.Resolve<IConversation>();
        conversation.Pause();
    }
}

Now this has some Unity specific code in it where it calls to the container to resolve the Conversation but the important part is that OnEntry to a method with the BusinessConversation attribute the conversation is resumed. This will either resume an existing conversation that’s stored in the session or it will create a new one. When it exits the method the conversation is paused which commits the transaction to the database.

The other code where it does resolution checking on HttpContext or reads from my UntityTestContainer does not really make me happy and eventually will be refactored to be cleaner in one way or another. But this code would be specific to how you interact with your container that would store the Conversations.

The only other issue I have with any of this is exposing the Session from the Conversation, I couldn’t figure out a simplier way for allowing my data providers to actually interact with the session to physically hit the database otherwise. I considered instead of a Conversation containing a session to actually make it implement ISession and assign it to itself during the creation but that didn’t sound right to me even more so.

I’d be grateful for any feedback on my implementation here: good, bad or indifferent. I feel I’ve finally put together a clean simple to understand post on how to correctly manage NHibernate’s session that doesn’t require implementing the UnitOfWork pattern and leverages dependency injection to keep the code seperated and decoupled as much as possible. The full source to my project as always can be found on my Assembla page.

kick it on DotNetKicks.com

Shout it

BloggingContext.ApplicationInstance.CompleteRequest();

10 thoughts on “Conversation Per Business Transaction using PostSharp and IoC

  1. pretty cool, I just have some questions and comments.

    Why do you use the InjectionConstructorAttribute? Unity by default looks for the most complex constructor to inject, so do most other IoC frameworks.

    How do you end a conversation or do you ever?

    PostSharp as an OnMethodError or something like that, you might want to abort your conversation in that case in your aspect.

    I had problems using Unity inside of PostSharp (but I was using 1.5 CTP). Did you run into the same problems?

    if you are concerned about IoC dependency, the P&P guys created a ServiceLocator interface to abstract your IoC away.

  2. I didn’t know it could function without the attributes unless I set up the config file, I’ll have to test that out. But it might be futile since I am going to replace Unity with StructureMap in the next few days.

    Since the Conversation is stored inside the HttpSession it will expire itself when the session ends and since the conversation implements IDisposable that it will call End on itself during disposing. Unless I’m making more assumptions than I should be on how the GC/session ending operations trigger work.

    One question I wonder is if I should disconnect the session in the Pause method or if it ends up doing that anyway.

    Pointing out the OnMethodError that does seem like a good place to put an abort to rollback the transaction.

    The problems I ran into with PostSharp was the fact that it has to run parts of your project to compile the PostSharp emitted IL. I tried having Unity inject the Conversation directly inside the constructor of my BusinessConversation attribute but I could never get it to resolve which is why I have the duplicated code in my attribute.

    I tried to find that ServiceLocator I searched for it both on Google and DotNetKicks but was coming up empty, I’ll try to find it on the P&P website and hopefully be able to find it again.

  3. My big issue with the conversation pattern is the Commit right in the middle of a conversation. As far as I’m concerned, the conversation isn’t over until its ended. Once its ended, then it should be committed. A pause is just that – a blip in the conversation.

    Really, a pause should serialise all the objects in use by the conversation and then deserialise them once the conversation is resumed.

    • This is an interesting comment Graham, but this is effectively what is happening. When you call commit this doesn’t actually write any data to the database (when you have FlushMode.Never set) it only commits your data to the First Level Cache in NHibernate (which is the ISession itself). I couldn’t tell you exactly what NHibernate does with this but you could dig through the source if you were that curious. Then after this since the Conservation object’s life cycle is managed by StructureMap and it is bound to the HttpSessionState by StructureMap the entire Conservation is serialized and stuffed into the server.

      This is how it works overall that you could rollback your ISession if someone hit cancel during a wizard process and not have to clean any data up from your database.

Leave a comment