2014-11-27

The transaction pattern and the feature badly missing from exceptions.

Exceptions are the best thing since sliced bread.  If you use them properly, you can write code of much higher quality than without them.  I think of the old days before exceptions, and I wonder how we managed to get anything done back then.  There is, however, one little very important thing missing from implementations of exceptions in all languages that I know of, and it has to do with transactions.

At a high level, exception handling looks structurally similar to transactional processing.  In both cases we have a block of guarded code, during the execution of which we acknowledge the possibility that things may go wrong, in which case we are given the opportunity to leave things exactly as we found them. So, given this similarity, it is no wonder that one can nicely facilitate the other, as this sample code shows:

Transaction transaction = transactionable.newTransaction();
try
{
    ...
}
finally
{
    transaction.close();
}

Note that the transaction illustrated above is a simple type of open-close transaction, not a database-style transaction. Database-style transactions are a bit more complicated, because they cannot be just closed, they need to be either committed, or rolled back. The collaboration between exception handling and database-style transactional processing is just slightly more involved:



Transaction transaction = transactionable.newTransaction();
try
{
    ...
    transaction.commit();
}
catch( Exception e )
{
    transaction.rollback();
    throw e;
}

That's all very nice, but it is a bit too verbose. So, the good folks who design languages have come up with a nifty feature: the try-with-resources construct of Java, or the using-IDisposable construct of C#, which allows you to have a method automatically invoked at the end of a block regardless of whether an exception was thrown or not, and greatly helps in keeping things neat by keeping cleanup code hidden.

I like this construct a lot, and I use it all the time. Actually, I practically use it in every single situation where I need to remember (or otherwise not fail) to do something at the end of something. The mere presence of such a need tells me that I have a transaction pattern in my hands, and I like to always code it as such.

For example, I have this special collection which issues a notification whenever it is changed, and I am about to make multiple changes to it, but I only want notifications to be issued once I am done with all the changes, not while I am doing the changes, so I have to somehow suppress notifications for a while, and then re-enable them. Someone else in my shoes would simply add a notification suppression flag to the notifying collection, and then code a statement at the beginning of the block which sets that flag, and another statement at the end of the block which clears that flag.  But not me. No, I have to have an AutoCloseable (in Java, or IDisposable in C#) object called `NotificationSuppression` instantiated at the beginning of the block, which sets the notification suppression flag from within its constructor, and gets automatically invoked at the end of the try-with-resources block (in Java, or using-IDisposable in C#) to clear the flag. The beauty of it is that if an exception is ever thrown in the mean time, the notifying collection will not be left in a permanent notification suppression state.

I suppose that the days when I used to care about clock cycles are long gone, and what I care most about now is reliability and elegance.

So, one might be tempted to guess that database-style transactions could also benefit from this construct, by having the transaction object automagically invoked at the end of the try-with-resources (in Java, or using-IDisposable in C#) block to provide proper termination for the transaction, saving us from having to write all that committing, exception-catching, rollbacking boilerplate code each time.  Right?  Well, unfortunately, wrong.  You cannot use try-with-resources (in Java, or using-IDisposable in C#) with database-style transactions, at least not in any elegant way.

The reason why you cannot do this is because your close() (in Java, Dispose() in C#) method has no way of knowing whether it is being invoked as a result of normal termination of the controlled block, or whether an exception has occurred, and so it has no way of deciding whether it should commit or rollback the transaction.

I suppose there are various different ways to fix this in future versions of Java and C#; one way that I can think of would be to introduce a new interface, say `AutoCloseable2` or `IDisposable2`, which accepts a throwable as a parameter, and operates in collusion with the compiler, as the case already is with the existing AutoCloseable and IDisposable.  If this throwable parameter is null, it would mean that the method is being invoked as a result of normal termination, while if it is not null, then it would mean that the method is being invoked as a result of abnormal termination, and the throwable is being passed just in case the method has any use for it.

True, in the majority of try-with-resources (in Java, or using-IDisposable in C#) blocks you do not need this special functionality, and true, such a feature would probably be misused by some. But that's all irrelevant; the point is that there is this very important and frequently used type of processing, namely, database-style transactional processing, which cannot be done the way things are now, at least not in an elegant way. I think that's worth fixing.

No comments:

Post a Comment