In the previous post; The Unit of Work Pattern I put down some thoughts on the Unit of Work pattern. In this post I delve into implementing a Unit of Work framework that allows persistence ignorance and provides a base for a unit of work component that I can use in Rhinestone.

Note: It would be highly rude of me to not mention that a lot of the ideas around UnitOfWork comes from Aynde’s excellent implementation of the pattern in Rhino.Commons. Even though Aynde’s implementation can be taken as is, I wanted to start with a framework that provided a bit more flexibility and a new twist on unit of work scoping…

Defining the interfaces:

Since we are all good developers here and follow the design principal of contract first / interface based development, lets started chalking out the IUnitOfWork interface.

   22 /// <summary>

   23     /// A unit of work contract that that commits / flushes changes to the store

   24     /// within a single transaction. The commit / flush should be called when the

   25     /// implementation is disposed.

   26     /// </summary>

   27     public interface IUnitOfWork : IDisposable

   28     {

   29         #region properties

   30         bool IsInTransaction { get; }

   31         #endregion

   32 

   33         #region methods

   34         /// <summary>

   35         /// Instructs the <see cref="IUnitOfWork"/> instance to being a new transaction.

   36         /// </summary>

   37         /// <returns></returns>

   38         ITransaction BeginTransaction();

   39         /// <summary>

   40         /// Instructs the <see cref="IUnitOfWork"/> instance to being a new transaction

   41         /// with the specified isolation level.

   42         /// </summary>

   43         /// <param name="isolationLevel">One of the values of <see cref="IsolationLevel"/>

   44         /// that specifies the isolation level of the transaction.</param>

   45         /// <returns></returns>

   46         ITransaction BeginTransaction(IsolationLevel isolationLevel);

   47         /// <summary>

   48         /// Flushes the changes made in the unit of work to the data store.

   49         /// </summary>

   50         void Flush();

   51         /// <summary>

   52         /// Flushes the changes made in the unit of work to the data store

   53         /// within a transaction.

   54         /// </summary>

   55         void TransactionalFlush();

   56         /// <summary>

   57         /// Flushes the changes made in the unit of work to the data store

   58         /// within a transaction with the specified isolation level.

   59         /// </summary>

   60         /// <param name="isolationLevel"></param>

   61         void TransactionalFlush(IsolationLevel isolationLevel);

   62         #endregion

   63     }

In keeping things simple, the IUnitOfWork interface mainly defines methods to ask the implementation to simply start transactions and provide an instance that implements from ITransaction interface (shown below). There are also two methods that flush any changes made to entities retrieved via the IUnitOfWork instance in a transactional manner.

   21 ///<summary>

   22     /// Encapsulates a data store transaction. Used by <see cref="IUnitOfWork"/> to flush changes

   23     /// under a transaction.

   24     ///</summary>

   25     public interface ITransaction : IDisposable

   26     {

   27         /// <summary>

   28         /// Commits the changes made to the data store.

   29         /// </summary>

   30         void Commit();

   31 

   32         /// <summary>

   33         /// Rollsback any changes made.

   34         /// </summary>

   35         void Rollback();

   36     }

Beyond the above two interfaces another interface is required for a factory interface. This interface will be responsible for creating instances of the IUnitOfWork interface:

   21 /// <summary>

   22     /// Factory interface that the <see cref="UnitOfWork"/> uses to create instances of

   23     /// <see cref="IUnitOfWork"/>

   24     /// </summary>

   25     public interface IUnitOfWorkFactory

   26     {

   27         /// <summary>

   28         /// Creates a new instance of <see cref="IUnitOfWork"/>.

   29         /// </summary>

   30         /// <returns></returns>

   31         IUnitOfWork Create();

   32     }

Nothing fancy there, just one simple Create() method that asks the factory to create a unit of work instance.

The UnitOfWork class:

[
NOTE: This class uses some helper components that were described in previous posts. If you havn’t already taken a look at them, you might want to read the following posts for a complete understanding of the following code;

  1. Building a generic IoC wrapper
  2. Using generics and lambdas to create a Guard class

]

Lets start off with the UnitOfWork class. This is the main class that the service layer will use to start off unit of work sessions and Repositories will use this class to obtain the current running UnitOfWork instances

   10         public static class UnitOfWork

   11         {

   12 

   13         }

The UnitOfWork class is static which means that the intention is not to create instances of the UnitOfWork class. Instead this static class will contain static methods to manage the lifecycle of a unit of work.

The first and obvious method will be to start a unit of work session. So lets first create a test method that tests starting a unit of work via the UnitOfWork static class.

   14         [Test]

   15         public void Calling_Start_Starts_A_UnitOfWork_Instance()

   16         {

   17             Assert.That(!UnitOfWork.HasStarted);

   18             Assert.That(UnitOfWork.Current, Test.Is.Null);

   19 

   20             var uowInstance = UnitOfWork.Start();

   21 

   22             Assert.That(UnitOfWork.HasStarted);

   23             Assert.That(uowInstance, Test.Is.Not.Null);

   24             Assert.That(UnitOfWork.Current, Test.Is.Not.Null);

   25             Assert.That(uowInstance, Test.Is.SameAs(UnitOfWork.Current));

   26         }

The test basically checks that there is no unit of work session currently running, which is done by checking the HasStaqrted property and checks the Current property on the UnitOfWork class to return null, then starts the a new unit of work and does checks to ensure that the Current property returns a non null reference and that the IUnitOfWork instance returned by the Start() method is the same as the IUnitOfWork instance returned by the Current property. I know the properties and methods in the unit test above don’t exist on the UnitOfWork class just yet… but that’s what TDD is isn’t it?

Okay, so the first step is to implement the HasStarted and Current property on the UnitOfWork class:

   24         #region const

   25         /// <summary>

   26         /// The Key used to store the current unit of work in <see cref="Storage.Local"/>.

   27         /// </summary>

   28         private static readonly string currentUnitOfWorkKey = "CurrentUnitOfWorkSession.Key"; //The key used to store the current unit of work in the session.

   29         #endregion

   30 

   31         #region properties

   32         /// <summary>

   33         /// Gets a boolean value indicating whether a _unitOfWork has been started for the current

   34         /// thread or current session.

   35         /// </summary>

   36         /// <value>

   37         /// True if a _unitOfWork has already started for the current thread or request.

   38         /// </value>

   39         public static bool HasStarted

   40         {

   41             get

   42             {

   43                 return Storage.Local.Contains(currentUnitOfWorkKey);

   44             }

   45         }

   46 

   47         /// <summary>

   48         /// Gets the current <see cref="IUnitOfWork"/> instance.

   49         /// </summary>

   50         /// <value>

   51         /// A <see cref="IUnitOfWork"/> instance for the current thread or request.

   52         /// </value>

   53         public static IUnitOfWork Current

   54         {

   55             get

   56             {

   57                 if (!HasStarted)

   58                     return null;

   59                 return Storage.Local.Get<IUnitOfWork>(currentUnitOfWorkKey);

   60             }  

   61             set

   62             {

   63                 if (value == null)

   64                     Storage.Local.Remove(currentUnitOfWorkKey); //Remove if the value is sepcified as null

   65                 else

   66                     Storage.Local.Set(currentUnitOfWorkKey, value);

   67             }

   68         }

   69         #endregion

NOTE: The implementation of the Unit of Work pattern here assumes that a Unit of Work session is local to the current thread / web request. If you require a Unit of Work to span multiple multiple web requests and use Session affinity, you can easily modify the implementation here to support session level unit of work instances.

The HasStarted property simply checks the current local storage, a helper class explained in post: Building a generic IoC wrapper that encapsulates storing application specific data to the thread / web request storage (or even Session and Application level). The Current property simply checks if a HasStarted returns true, if so it retrieves the IUnitOfWork instances from the Storage and returns that instance. More interestingly, the Current property also has a public Set accessor. This allows components to register their own IUnitOfWork instances that may be started outside the scope of the UnitOfWork static class.

Now that HasStarted and Current properties are implemented, the next step is to focus on the Start method of the UnitOfWork class. The Start method will first get a IUnitOfWorkFactory instance from the IoC container and request it to create a new IUnitOfWork instance:

   72         /// <summary>

   73         /// Starts a new <see cref="IUnitOfWork"/> session that implements a unit of work operation.

   74         /// </summary>

   75         /// <returns></returns>

   76         public static IUnitOfWork Start ()

   77         {

   78             if (HasStarted)

   79                 return Current; //returning the current uniut of work if it has already been started.

   80             IUnitOfWorkFactory factory = IoC.Container.Resolve<IUnitOfWorkFactory>();

   81             Current = factory.Create();

   82             return Current;

   83         }

Lets modify the unit test to setup the IoC container and add additional checks to ensure that the IUnitOfWork instance returned by the UnitOfWork.Current property is indeed the same IUnitOfWork instance returned by the IUnitOfWorkFactory instance

I’ve added some more tests in the source to ensure that if a UnitOfWork has already started by one component, when another component issues a Start statement again, it should return the same UnitOfWork instance.

   15         [Test]

   16         public void Calling_Start_Starts_A_UnitOfWork_Instance()

   17         {

   18             Assert.That(!UnitOfWork.HasStarted);

   19             Assert.That(UnitOfWork.Current, Test.Is.Null);

   20 

   21             var mockUOWFactory = MockRepository.GenerateStub<IUnitOfWorkFactory>();

   22             var mockUOWInstance = MockRepository.GenerateStub<IUnitOfWork>();

   23             mockUOWFactory.Stub(x => x.Create()).Return(mockUOWInstance);

   24 

   25             var mockIoCInstace = MockRepository.GenerateMock<IoC>();

   26             mockIoCInstace.Stub(x => x.Resolve<IUnitOfWorkFactory>()).IgnoreArguments().Return(mockUOWFactory);

   27 

   28             IoC.SetContainer(mockIoCInstace);

   29 

   30             var uowInstance = UnitOfWork.Start();

   31 

   32             Assert.That(UnitOfWork.HasStarted);

   33             Assert.That(uowInstance, Test.Is.Not.Null);

   34             Assert.That(UnitOfWork.Current, Test.Is.Not.Null);

   35             Assert.That(uowInstance, Test.Is.SameAs(UnitOfWork.Current));

   36             Assert.That(uowInstance, Test.Is.SameAs(mockUOWInstance));

   37         }

   38 

   39         [Test]

   40         public void Calling_Start_On_Already_Started_UnitOfWork_Returns_Same_UnitOfWork ()

   41         {

   42             var mockIoCContainer = MockRepository.GenerateStub<IoC>();

   43             var mockUOWFactory = MockRepository.GenerateStub<IUnitOfWorkFactory>();

   44             var mockUOWInstance = MockRepository.GenerateStub<IUnitOfWork>();

   45 

   46             mockIoCContainer.Stub(x => x.Resolve<IUnitOfWorkFactory>()).Return(mockUOWFactory);

   47             mockUOWFactory.Stub(x => x.Create()).Return(mockUOWInstance);

   48 

   49             IoC.SetContainer(mockIoCContainer);

   50 

   51             IUnitOfWork uowInstance = UnitOfWork.Start();

   52             Assert.That(UnitOfWork.HasStarted);

   53             Assert.That(UnitOfWork.Current, Test.Is.SameAs(uowInstance));

   54             Assert.That(UnitOfWork.Start(), Test.Is.SameAs(uowInstance));

   55         }

[Note: You may notice that I am not dealing with database connections here. This is a explicit design decision because I believe the implementing IUnitOfWork instance should be the one that decides on the connection string to use. The decision on which connection string(s) to use is often based on the environment an application is running on and sometimes involves application level logic]

Okay, now that we have a Start method, lets deal with a Finish method. Logically once you start a UnitOfWork there must be a way to end one so that all changes are flushed to the database. Here are some unit tests to test the implementation, before the implementation:

   57         [Test]

   58         public void Calling_Finish_Without_Start_Should_Throw_InvalidOperationException()

   59         {

   60             Assert.That(!UnitOfWork.HasStarted);

   61             Assert.That(UnitOfWork.Current, Test.Is.Null);

   62             Assert.That(() => UnitOfWork.Finish(false), Test.Throws.Exception<InvalidOperationException>());

   63         }

   64 

   65         [Test]

   66         public void Calling_Finish_When_UnitOfWork_Started_Finishes_Current_UnitOfWork_An_Resets_Current_UnitOfWork()

   67         {

   68             //Mock setups

   69             var mockIoCContainer = MockRepository.GenerateStub<IoC>();

   70             var mockUOWFactory = MockRepository.GenerateStub<IUnitOfWorkFactory>();

   71             var mockUOWInstance = MockRepository.GenerateStub<IUnitOfWork>();

   72 

   73             mockIoCContainer.Stub(x => x.Resolve<IUnitOfWorkFactory>()).Return(mockUOWFactory);

   74             mockUOWFactory.Stub(x => x.Create()).Return(mockUOWInstance);

   75 

   76             IoC.SetContainer(mockIoCContainer);

   77 

   78             IUnitOfWork uowInstance = UnitOfWork.Start();

   79             Assert.That(UnitOfWork.HasStarted);

   80             Assert.That(UnitOfWork.Current, Test.Is.Not.Null);

   81             Assert.That(uowInstance, Test.Is.Not.Null);

   82 

   83             UnitOfWork.Finish(true);

   84 

   85         }

Below is the implementation of the Finish method:

   87         /// <summary>

   88         /// Finishes the current IUnitOfWork instance.

   89         /// </summary>

   90         /// <param name="flush">bool. True if the Finish operation should flush the changes made

   91         /// to the current <see cref="IUnitOfWork"/> instance.</param>

   92         public static void Finish (bool flush)

   93         {

   94             Guard.Against<InvalidOperationException>(!HasStarted, "There is no running UnitOfWork session to finish.");

   95             if (flush)

   96                 Current.TransactionalFlush();

   97             Current.Dispose();

   98             Current = null;

   99         }

The one thing to Note in the Finish method is that I don’t explicitly call the BeginTransaction of the internal IUnitOfWork implementation. Since the intention of TransactionalFlush is flush all changes captured by the IUnitOfWork within a Tranaction, calling BeginTransaction() would be redundant.

Time for a Spike:

Now that we have a basic unit of work framework it’s time to do a spike and put the framework to test. I have updated the previous sample I had created to demonstrate persistence agnostic implementation of RepositoryBase (Post: Persistence Ignorance and DDD with RepositoryBase) and the source can be found here: https://rhinestone.googlecode.com/svn/samples/LinqRepositorySample

I basically updated the sample to implement a IUnitOfWork instance for both Linq and NHibernate. Then updated the tests to demonstrate that the you can easily switch between either Linq or NHibernate and tests pass regardless of the selection.

Some notes on the implementation for Linq though. First of all the DataContext has no real way to Rollback a transaction or Rollback any changes made to entities in the DomainContext. This makes implementing the Rollback method in ITransaction difficult as the Rollback method really doesn’t have any logic associated with it… the rollback basically disposes of the transaction.

Talking about transactions, both implementations will work with TransactionScope. So if you have a UnitOfWork instance working inside a TransactionScope, then any Commit call on the UnitOfWork will not succeed until the containing TransactionScope.Complete is called. That being said, the TransactionScope approach will only work in NHibernate if you’re underlying provider is the ADO.Net SQL Client provider. I know providers for other databases do provide some level of support for auto enlistment in transaction scope, but the support is spotty at best.

Coming up next…

In the next post I’m going to extend the current Unit of Work framework and add additional hooks to semantically specify unit of work scopes. This new additional will provide similar semantics as the TransactionScope, but still being persistence ignorant.

Posted on Friday, October 03, 2008 5:03 PM | Filed Under [ Patterns NHibernate DDD Repository Linq Architecture ]


Comments

Gravatar
# I discovered your blog today and have really been ...
Posted by Anonymous
on 12/5/2008 7:38 AM
I discovered your blog today and have really been enjoying your articles!<BR/><BR/>I agree that TransactionScope (and the underlying transaction) is not supported by all data source providers. However, support is far from "spotty" in my opinion. Most ADO.NET providers I've worked with support it including Oracle. There is even support with transactional file systems like NTFS. I also am not keen on mixing transactions in with the UnitOfWork although in your implementation, transaction management appears to be the only thing it is responsible for doing.
Gravatar
# Are you no longer blogging? This has been a great ...
Posted by Anonymous
on 12/1/2008 3:20 AM
Are you no longer blogging? This has been a great series and I'd love to see more.
Gravatar
# ONLY CHEAP!!!
on 2/13/2012 12:10 AM
Good post, keep going
Gravatar
# re: Implementing a persistence ignorant Unit of Work framework
Posted by Hermes bags
on 4/10/2012 2:32 AM
thanks for sharingx
Gravatar
# re: Implementing a persistence ignorant Unit of Work framework
Posted by Coach outlet
on 4/23/2012 11:47 PM
it's good for me , thanksX
Gravatar
# jordans xi
Posted by jordans xi
on 5/30/2012 11:39 PM
clinched the jordan retro 13 second division championship A larger crowd on a workday in the jordan retro 5 Loop jordan retro 5 could strain city jordan retro 12 resources, two jordan 13 sale platforms have emerged to promote that awareness. cheap jordan 12 A partner with Wood cheap jordan 13 LLP (www.lik...
Gravatar
# re: Implementing a persistence ignorant Unit of Work framework
Posted by Trustedessays
on 7/25/2012 11:55 PM
I always visit your blog because I learned different technical terms here and at the same time, I get to know more about Rhinestone that you keep on discussing here.
Gravatar
# re: Implementing a persistence ignorant Unit of Work framework
Posted by rolex replica
on 1/17/2013 2:18 AM
Dissimilar to almost all boys watches, this specific Work of hermes bags art design offers any hard to find watch in to the internal operation of your excellent custom made wristwatch. The many involved equipment in rolex replica addition to cogs tend to be about excellent present is actually a good 18k platinum circumstance, a better solution to hair extensions, “Hey, just what moment would it be? ”
Post Comment
Title *
Name *
Email
Url
Comment *  
Please add 5 and 3 and type the answer here: