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;
- Building a generic IoC wrapper
- 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.