Sergey Shishkin

on agile software development

Unit Of Work Pattern Revised

The last post in my data access trilogy (see also repository and specification) is about the Unit of Work pattern.

When working with NHibernate you have a unit of work for free as its ISession interface. Moreover, one have to use ISession in some way in order to get something useful out of NHibernate. However, using it directly in the application layer is very dangerous. The application gets coupled to NHibernate forever. It’s nothing even close to the Onion Architecture.

Another approach would be to introduce yet another layer of abstraction. This general approach can solve any problem except the problem of too many layers of abstraction. There should be a simpler way to manage connections, transactions and units of work, and all that not coupled to NHibernate!

Back To The Beginning

Let’s assume for a moment that we couple the application to NHibernate. Then what we might do in an application layer service method is something like this:


using
(var session = SessionFactory.OpenSession())

using (var tx = session.BeginTransaction())

{

    //…

 

    session.Flush();

    tx.Commit();

}

Now in that using scope we have a session instance that we use to instantiate repositories and do all the work. Repositories don’t have to manage neither connections, nor transactions, nor units of work. They just write to the provided session.

Transactions Are Units Of Work

An important fact to notice about the code snippet above is that it runs the unit of work in a transaction. So why then we have to code against two separate abstractions (NHibernate’s ISession and ITransaction) when they both mean the same concept (unit of work) to the application?

A much better way would be to use only one abstraction instead and let it manage the others under the hood. But wait starting to code the IUnitOfWork interface. There is a yet simpler solution.

Plain old .NET ambient transactions (which I assume every .NET developer is already familiar with) can represent units of work for the application layer:


using
(var tx = new TransactionScope())

{

    //…

 

    tx.Complete();

}

The application layer then has no dependencies whatsoever to NHibernate. As a bonus we get a transaction across any resources that support ambient transactions (MSMQ, for example) (although transactions over multiple resources come with the price of Distributed Transactional Coordinator).

It is now responsibility of the repository implementation to obtain an appropriate session (get current or open new) whenever the repository is called. The transaction’s completed event can be used to flush the session if the transaction has been committed and close it to free the connection.

Logging And Auditing

But wait, what about logging? How can I log an error to a transactional store, if it automatically gets rolled back? To suppress the current ambient transaction in special cases (like logging) the TransactionScope supports options:


using
(var noTx = new TransactionScope(TransactionScopeOption.Suppress))

{

    log.Write(exception);

 

    noTx.Complete();

}

Isolation Level

There is also an overload to specify the isolation level of the transaction, when tuning performance of concurrent requests:


using
(var tx = new TransactionScope(

    TransactionScopeOption.Required,

    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))

{

    // read-only operation here

 

    tx.Complete();

}

Summary

In this post I described how one can reuse the common .NET abstraction of ambient transactions as a unit of work and thus hide the infrastructure away from application developers and avoid introducing new abstractions into the application. In this approach coupling is inverted relative to the first code sample. Now the session (NHibernate-specific unit of work) is coupled to the .NET ambient transaction (application unit of work), and not the other way around. Still one can implement advanced connection management under the hood to support batching of multiple transactions (units of work) in a single database connection.

Series Summary

In the last three posts (see also repository and specification) I described how one can implement data access infrastructure while keeping the rest of the application independent of it. I use NHibernate as the O/R Mapper of choice, though the principles described here are applicable to almost any .NET data access technology. Once again, the principles are:

  • Use the repository pattern to abstract the data access infrastructure;
  • Use Linq and the specification pattern to abstract queries;
  • Use .NET ambient transactions as units of work.

When sticking to these principles, the specific data access infrastructure (be it NHibernate or anything) can be hidden away from application developers and hence lower the learning curve in a project. Of course, somebody still has to deal with actual mappings, but it can be the DBA! 😉

 

Advertisements

Written by Sergey Shishkin

17.08.2008 at 20:25

Posted in Data Access

%d bloggers like this: