NServiceBus with NHibernate and MySQL

In the last project I have been working i finally got a chance to design and implement a solution based on NServiceBus and NHibernate, two tools I’ve been watching for a while but never got a chance to play with in more than sample applications. For some external reasons I've been forced to use MySQL as a database server in this project.

So basically I’m using NServiceBus to provide reliable communication between the involved components and NHibernate to do the persistence of the domain objects used by the components. Up to this point the whole design of the solution looks good, with minimal effort i have reliable, fault tolerant services that are ready to do their job.

Now i start implementing the details and get to the point where MySQL comes into play. I must say, it has surprised me... both ways.

The good thing is that after careful tuning, where my previous UNIX experience had a very important role, the database is able to handle the amounts of data that i plan to throw at it. Also it surprised me that some pretty complex queries run a lot faster than expected.

Then the bad things started to show up. The hardest to debug was that updating an indexed column from multiple parallel transactions causes deadlocks witch cause transactions to be aborted. Of course this only happens at high loads. It was not hard to avoid this after i found out what the problem was ... but still after this i had a feeling of working with something that might not be as reliable as expected.

After that the MySQL .NET connector dropped the bomb on me: Distributed Transactions are not supported. Ok, they are not supported but why the hell does the connector throws an exception when used in a distributed transaction? I can understand that i can’t rely on the MySQL transaction being enlisted in the Distributed Transaction (DT) and that i have to handle that myself but not being able to use the connector AT ALL under a DT was unexpected. At this point i see only one solution: grab the source for the connector and modify the part that checks if a DT is present and just ignore it. Turns out this was very easy to do. If anyone is interested in this change in the connector i can provide more details.

Now i need to find a way of having a NHibernate ISession and a ITransaction per NServiceBus message handler.

The first approach was something similar to what Andreas Öhlund describes in this blog post. The only problem is that IMessageModule implementations in NServiceBus 2.0 are singletons and that was a problem because i need to store the ITransaction to commit or rollback at the end of the message handler. If in the next version of the NServiceBus there will be a way to have some message handler “wrapper” that it could work.

My solution was to use a base abstract class for the massage handlers. So instead of just implementing IMessageHandler now i derive from this base class. The code below should speak for itself:

 1 /// <summary>
 2 /// Base class for message handlers.
 3 /// Manages the unit of work required for handling the message.
 4 /// </summary>
 5 public abstract class MessageHandler<T> : IMessageHandler<T>
 6           where T : IMessage
 7 {
 8     /// <summary>
 9     /// The injected unit of work implementation.
10     /// </summary>
11     private readonly IUnitOfWork unitOfWork;
12 
13     public MessageHandler(IUnitOfWork unitOfWork)
14     {
15         this.unitOfWork = unitOfWork;
16     }
17 
18     /// <summary>
19     /// Concrete handlers must implement this method.
20     /// </summary>
21     public abstract void HandleMessage(T message);
22 
23     public void Handle(T message)
24     {
25         try
26         {
27             HandleMessage(message);
28             unitOfWork.Complete();
29         }
30         catch
31         {
32             unitOfWork.Abort();
33             throw;
34         }
35         finally
36         {
37             unitOfWork.Dispose();
38         }
39     }
40 }

The implementation for the IUnitOfWork is in this case very simple, providing only the creation of the session and the transaction and the required operations. Since the unit of work is created per handler and the handlers don’t use other threads to do the work I don’t need to worry about making it thread safe.

 1 public class MessageUnitOfWork: IUnitOfWork
 2 {
 3     private readonly ISessionFactory factory;
 4     private readonly ITransaction transaction;
 5     private readonly ISession session;
 6 
 7     public MessageUnitOfWork(ISessionFactory factory)
 8     {
 9         this.factory = factory;
10         session = factory.OpenSession();
11         CurrentSessionContext.Bind(session);
12         transaction = session.BeginTransaction();
13     }
14 
15     public void Complete()
16     {
17         transaction.Commit();
18     }
19 
20     public void Abort()
21     {
22         transaction.Rollback();
23     }
24 
25     public void Dispose()
26     {
27         transaction.Dispose();
28         CurrentSessionContext.Unbind(factory);
29         session.Dispose();
30         GC.SuppressFinalize(this);
31     }
32 }

There is still one small problem. If the handler finishes it’s work without and exception and the mysql transaction is committed BUT an exception is thrown by the bus when committing the distributed transaction the MySQL transaction is not rolled back. But i realized that this only means that the same message might be sent again to the handler and that the handlers in general should handle this logical case since whoever sent the message is free to send it multiple times.

Since I’ve got this solution working it has handled a few millions of messages and there have been crashes and transaction that got rolled back occasionally but after all the system is designed to be fault tolerant and it has proven it is. Also in all the cases the database remained in a consistent state, witch in the beginning i was not sure it will.

In the end i would like to thank the NServiceBus team ( mainly Udi Dahan and Andreas Öhlund ) witch was very responsive and helpful on the support mailing list. I can only hope to find the time to contribute a few ideas to the next release of NSB. Also i would like to thank the NHibernate team for the great product they created ( can’t wait for the 3.0 ).

Comments