Pattern: Transaction Object

Abstract

Transaction Objects provide a clearly separated interface for transactions in the context of database access layers.

Context

You applied the Relational Database Access Layer Framework. You need to define an interface for the start, commit and abort of transactions.

Problem

What will the interface for transactions look like?

Forces

Solution

Use a transaction object. Have it rollback the transaction in its destructor so that open transactions are aborted in any case if not committed explicitly.

Structure

The Transaction is responsible for flushing the ViewCache when a transaction is started. During the transaction the ViewCache is filled with View objects. When a transaction is committed, the Transaction object issues a writeAndFlush() command to the ViewCache.

Dynamic Behavior

As an example we will show the lifecycle of a transaction object that is created by a main program. The locking strategy used is optimistic. The scenario is given by the following piece of user code.

 


void main ( void ) {

try {

       Transaction trans;

       trans.start();

           Order::printInvoice("47613","invoice.txt");

       trans.commit();

}

catch (...) {

       // trans is automaticaly destroyed here, resulting in an abort of the transaction

}

};

This results in the below interaction diagram.

Consequences

Encapsulation: The Transaction objects allows clean encapsulation of transaction behavior at the cost of a relatively simple class. The use of a class also ensures that instances are destroyed at the end of a block. Transactions cannot be left open by negligence or in case of an unexpected exception.

Implementation

The transaction object should be implemented as a Singleton [GOF95] as relational databases will usually not allow you to use nested transactions. Use the Singleton to prevent users from creating more than one (nested) transaction at a time.

You should also control the protocol of a transaction (no commit before a transaction is started) by using an internal state machine. The State Pattern [GOF95, Dys+96] may be used to avoid conditional code in Transaction objects due to dealing with internal states of the transaction like undefined, started, or aborted.

Consider using the Strategy Pattern [GOF95] if you want to give the API’s user the option to choose a locking policy for each transaction.

Variants

The interaction with the database is dependent on the locking policy used. In case of optimistic locking, the database transaction will be started just before the writeAndFlush() command is issued to the ViewCache. If the designer considers pessimistic locking more appropriate, the database transaction is started right after the ViewCache::flush() command resulting in a longer span of the transaction. Usually you will implement only one locking policy for an application. If you are building a reusable framework you should think of parameterizing the locking policy.

Related Patterns

Brown and Whitenack describe a similar mechanism, called Transactions as Blocks, for Smalltalk [Bro+96].

Fowler has another version on the web and calls it the Unit of Work pattern. Beware that transactions that need to register objects are not very slick but explicit object registration to transaction objects can be found in quite a few access layers.

Optimistic Locking is a method often needed below transaction objects

Known Uses

The interface shown here can also be found in the ODMG C++ language binding for object databases [ODMG96].