This post assumes you are aware of Dependency Injection pattern and what Inversion of Control containers are. If not, I would recommend reading this post: Building a generic IoC wrapper or google Dependency Injection and IoC to get some background information.
Okay anyone who follows DDD, patterns or cares about data access knows about the Unit of Work pattern. The concept isn’t hard especially when you’ve had to work with transaction and synchronization issues when dealing with a single logical operation that is composed of multiple database operations.
The problem.
Consider you have a order dispatch application that consumes a B2B order and for each order has to do various operations:
- For each item in the order check the inventory for quantity in stock.
- When there is enough quantity in stock for an item, update the stock for that item to indicate quantity that has been reserved for this order and deduct the total quantity available.
- In case there isn’t enough quantity in stock for an item then whatever is available should be reserved and the for the remaining quantity a item refill order should be generated and dispatched so that the item can be restocked. The order then should be updated to indicate back-order quantity.
- Generate a dispatch order that is sent out to the warehouse for dispatching. The dispatch order should contain the shipping address for the Customer.
Now all this operations above logically fall under one operation: Process Order. While processing an order either all actions succeed or none. Meaning they run under one transaction. Simple enough right? Now lets assume being a good developer you are who really, I mean REALLY, cares about separation of concerns and contract driven development, develop this order dispatching system as dependency agnostic components that do a single operation. So the application might end up with many different components that perform multiple data operations, but all those database operations must operate within one singe transaction and any changes made to the database should fall under a single operation. If even on of those database changes do not go through, for whatever reason that may be, none should.
The way I used to do it…
Okay so way back when I didn’t know about patterns and just about the only thing that mattered to me was shipping the damn thing cause my boss is breathing down my neck that we had promised our customer that the order dispatch system will be delivered by the end of the week, which of course means that I was told about this new application that I had to develop just 2 days about, to ensure all operations fall under one transaction I would pass around a IDbConnection and a IDbTransacton…
11 public interface IInventoryRepository
12 {
13 ItemInventory GetItemInventory(Item item, IDbConnection connection, IDbTransaction transaction);
14 void UpdateItemInventory(ItemInventory inventory, IDbConnection connection, IDbTransaction transaction);
15 void PlaceItemRefreshOrder(Item item, RefreshOrder order, IDbConnection connection, IDbTransaction transaction);
16 }
17
18 public interface IDispatchOrderRepository
19 {
20 void SaveDispatchOrder(DispatchOrder order, IDbConnection connection, IDbTransaction transaction);
21 }
22
23 public interface IOrdersRepository
24 {
25 Order GetNextOrder(IDbConnection connection, IDbTransaction transaction);
26 void UpdateOrder(Order order, IDbConnection connection, IDbTransaction transaction);
27 }
The above code snippet shows three interfaces of would be repositories of this application. Each method in the repository interface accepts a IDbConnection and IDbTransaction instance that the repository uses to issue commands to the database.
15 public void ProcessOrder(Order order)
16 {
17 using (IDbConnection connection = ConnectionProvider.GetConnnection())
18 {
19 connection.Open();
20 using (IDbTransaction transaction = connection.BeginTransaction())
21 {
22 try
23 {
24 IInventoryRepository inventoryRepository = GetInventoryRepository();
25 DispatchOrder dispatchOrder = new DispatchOrder();
26 foreach (var item in order.Items)
27 {
28 order.Status = "In Process";
29 ItemInventory inventory = inventoryRepository.GetItemInventory(item, connection, transaction);
30 //Process the inventory.. then update it....
31 inventoryRepository.UpdateItemInventory(inventory, connection, transaction);
32
33 //Code edited for brevity... create a dispatch order and save both the dispatch order and the base order
34 dispatchRepository.SaveDispatchOrder(dispatchOrder, connection, transaction);
35 }
36
37 transaction.Commit();
38 }
39 catch (Exception)
40 {
41 transaction.Rollback();
42 }
43 }
44 }
45 }
The ProcessOrder method shown above can serve as an example of how the Process Order operation would be responsible for maintaining the connection and transaction used for each repository action. [Yes I could use TransactionScope in the above and do away with passing IDbTransaction to the repositories but the problem still remains that the connection management is now in the hands of my business logic layer]
Issues with the previous approach.
The biggest problem I have with the above approach is that a) I have introduced connection management and transaction concerns into by business logic or service layer and b) I have leaked infrastructure concerns into all layers of my application.
The other issue is that each individual update call made to the repository causes an actual update operation to be sent to the database. Ideally I would like to have a method that could be called “SubmitChanges” that would send all necessary updates to the database at one go and would then rollback all changes if the operation did not succeed. This also means that when an operation spans multiple requests or sessions the connection and transactions have to remain open which is impractical and definitely not recommended.
Introducing the Unit Of Work pattern.
The idea behind the unit of work pattern is to introduce a dedicated component, i.e. the UnitOfWork, that keeps track of changes made to domain entities and then is responsible for persisting those changes to the database under a single transaction.
The Unit of Work pattern operates on two simple principles; 1. Changes made to the domain model via the domain entities are considered to be under a Business Transaction where changes are made in memory and not persisted to the database and 2. Once the Business Transaction is completed, all changes are persisted to the database under a single database transaction.
The concept of a Business Transaction is analogous to a database transaction. Where a database transaction groups multiple SQL statements as a single operation, i.e. either all succeed or all rollback, similarly a business transaction spans all in-memory operations and actions that are considered as a business operation. In the above order dispatching example the Process Order would be considered as a Business Transaction.
Below is an example of what a very simple UnitOfWork component may look like:
15 public interface IUnitOfWork
16 {
17 void AddNew(object Entity);
18 void Update(object entity);
19 void Delete(object entity);
20 void Commit();
21 void Rollback();
22 }
What the above interface suggests is that the UnitOfWork component simply tracks changes to domain entities via the AddNew / Update / Delete methods and once the the business transaction is done a Commit is called to send all changes to the database, or perform a Rollback if they don’t succeed.
Right now there might be a little voice in you’re head saying how does the IUnitOfWork component know how to persist the changes. In the above example you could use a Data Mapper pattern that provides the logistics of mapping the necessary operation, i.e. Insert / Update / Delete, for an entity type. The example below might make things clearer:
19 public interface IUnitOfWork
20 {
21 void AddNew(object entity);
22 void Update(object entity);
23 void Delete(object entity);
24 void Commit();
25 void Rollback();
26 }
27
28 public interface IEntityMapper
29 {
30 void Insert(object entity, IDbConnection connection);
31 void Update(object entity, IDbConnection connection);
32 void Delete(object entity, IDbConnection connection);
33 }
34
35 public class BasicUnitOfWork : IUnitOfWork
36 {
37 #region fields
38 private HashSet<object> _newEntities = new HashSet<object>();
39 private HashSet<object> _updatedEntities = new HashSet<object>();
40 private HashSet<object> _deletedEntities = new HashSet<object>();
41 #endregion
42
43 public void AddNew(object entity)
44 {
45 _newEntities.Add(entity);
46 }
47
48 public void Update(object entity)
49 {
50 _updatedEntities.Add(entity);
51 }
52
53 public void Delete(object entity)
54 {
55 _deletedEntities.Add(entity);
56 }
57
58 public void Commit()
59 {
60 using (TransactionScope scope = new TransactionScope())
61 {
62 IDbConnection connection = GetConnection();
63
64 //Perform Inserts
65 foreach (var insert in _newEntities)
66 {
67 IEntityMapper mapper = IoC.Container.Resolve<IEntityMapper>(insert.GetType());
68 mapper.Insert(insert, connection);
69 }
70
71 //Perform Updates
72 foreach (var update in _updatedEntities)
73 {
74 IEntityMapper mapper = IoC.Container.Resolve<IEntityMapper>(update.GetType());
75 mapper.Update(update, connection);
76 }
77
78 //Perform Deletes
79 foreach (var delete in _deletedEntities)
80 {
81 IEntityMapper mapper = IoC.Container.Resolve<IEntityMapper>(delete.GetType());
82 mapper.Delete(delete, connection);
83 }
84 scope.Complete();
85 }
86 }
87
88 public void Rollback()
89 {
90 _newEntities.Clear();
_updatedEntities.Clear();
_deletedEntities.Clear();
91 }
[Update: shereef in the comments pointed out that Rollback should actually clear the Hastables containing the updates, which is correct. I’ve updated the code to clear them out in the Rollback method call.]
The BasicUnitOfWork component shown in the example above merely adds entities to internal HashSet collections that indicate whether the entity should be either Inserted / Updated / Deleted. In the Commit method, it enumerates each list and for each item in a list the component attempts to get a IEntityMapper that can handle mapping database operations for the specified entity type. Once it gets a mappper, it delegates the appropriate call, i.e. Insert / Update / Delete, to the mapper and finally commits.
The Unit of Work pattern with ORM frameworks
The above example is fine when working with the ADO.Net stack directly, but with the popularization of the ORM tools out there there is a movement to use more and more of these ORM framework to simplify the data access layer.
Most ORM frameworks, at least the ones I know of, implement the Unit of Work pattern directly within their framework, allowing you to concentrate on the actual business and service layer of your application. For example, in Linq to SQL the DataContext class can be thought of as a Unit of Work implementations (same for the ISession interface in NHibernate). Changes you make to the entities retrieved via a DataContext instance is tracked using the DataContext’s change tracking functionality, but those changes are not persisted until you call the SubmitChanges method on the DataContext.
In such a scenario where ORM frameworks are being used along with a Repository pattern, our UnitOfWork component’s responsibilities are slightly altered. Taking Linq to SQL as an example (you can easily apply the same thing to NHibernate); say we were to refactor the order dispatching example above to use Linq to SQL, Repositories and Unit of Work pattern, then the requirements from our Unit of Work component changes. First of all we don’t need the AddNew / Update / Delete methods since the DataContext takes care of that for us. Second, is to allow the Repository to ask the current running Unit of Work to give it the DataContext it should use. So after refactoring the IUnitOfWork and it’s implementation looks like this:
20 public interface IUnitOfWork
21 {
22 DataContext CurrentContext { get; }
23 void Commit();
24 void Rollback();
25 }
26
27 public class BasicUnitOfWork : IUnitOfWork
28 {
29 private DataContext _currentContext = new DataContext(GetConnection(), GetMappingSource());
30
31 public DataContext CurrentContext
32 {
33 get { return _currentContext; }
34 }
35
36 public void Commit()
37 {
38 _currentContext.SubmitChanges(); //No transaciton required since SubmitChanges uses an implicit transaction.
39 }
40
41 public void Rollback()
42 {
43 //Do nothing.
44 }
45 }
Basically I yanked out the AddNew / Update / Delete methods and added a get property for retrieving the DataContext that should be used. The next step is to find a way for repositories to be able to get the current running Unit of Work. For that we can create an static factory class that is responsible for starting, ending and getting the current unit of work. And to make things interesting lets make it support durable unit of work instances so that we can allow the unit of work to span multiple web requests.
20 public static class UnitOfWorkFactory
21 {
22 private const string SESSION_KEY = "UNITOFWORK_SESSION_CURRENT";
23
24 public static IUnitOfWork Current
25 {
26 get
27 {
28 return (IUnitOfWork) HttpContext.Current.Session[SESSION_KEY];
29 }
30 }
31
32 public static void Start()
33 {
34 HttpContext.Current.Session[SESSION_KEY] = IoC.Container.Resolve<IUnitOfWork>();
35 }
36
37 public static void Finsh ()
38 {
39 HttpContext.Current.Session.Remove(SESSION_KEY);
40 }
41 }
When the Start method is called on the UnitOfWorkFactory class, it basically asks the IoC container to get an instance of IUnitOfWork, which in our case will be the BasicUnitOfWork implementation, and saves that into a Session variable (making the unit of work durable).
Now repositories can use the UnitOfWorkFactory class to get the current Unit of Work and through it get the DataContext to use like so:
44 public class OrdersRepository : IOrdersRepository
45 {
46 public Order GetNextOrder ()
47 {
48 var unitOfWork = (BasicUnitOfWork) UnitOfWorkFactory.Current;
49 var ordersTable = unitOfWork.CurrentContext.GetTable(Order);
50 return (from order in ordersTable select order).FirstOrDefault();
51 }
52
53 public void AddOrder (Order order)
54 {
55 var unitOfWork = (BasicUnitOfWork) UnitOfWorkFactory.Current;
56 var ordersTable = unitOfWork.CurrentContext.GetTable(Order);
57 ordersTable.InsertOnSubmit(order);
58 }
59 }
Coming up next…
Hopefully the above explains the Unit of Work pattern well (and correctly) and there are tons of examples out there that implement the pattern. In the next post I will dive into implementing a persistent ignorant unit of work framework, with a slight twist, that will be used in Rhinestone.
PLEASE NOTE: The examples above are just that… examples. They are by no means complete implementations of the Unit of Work pattern or the Data Mapper pattern. For a complete implementation please see my upcoming post on this topic.
9 comments:
Why Rollback does nothing, while i think it should clear the Hashsets?
Shereef Sakr
great, you're back!
will you continue working on rhinestone again?
anonymous:
Thanks! It feels great to be back and blogging again.
Yes, I will be working on Rhinestone very soon. Recent posts have been around infrastructure and patterns that wold end being used in Rhinestone, so I wanted to get those out of the way first.
Going forward you will see more updates to Rhinestone's code base itself and more posts on Rhinestone here.
shereef:
You are absolutely right, the Hashtables should be cleared in the Rollback method. I've updated the post to indicate this.
Thanks for spotting this bug!
Ritesh:
This post and the related ones on UoW are a great example of how to implement the pattern.
I'm trying to implement UoW myself as described in PoEAA and (as a learning experience) are not using any ORM framework but instead simply using Data Mappers as explained (again) in PoEAA. However, i can't figure out how to correctly register objects as dirty with the unit of work.
I posted on the msdn forums (http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3995228&SiteID=1) and was referred to this blog from there. Other than what's already been suggested in the thread, does anyone from here have any suggestions as to what the best way to solve the problem i mention there is? Am i possibly missing something in the design that would implicitly solve the problem?
Matthew
What if your transaction span over multiple repositories ?
@Anonymous:
Take a look at these posts: http://www.codeinsanity.com/2008/10/implementing-persistence-ignorant-unit.html and http://www.codeinsanity.com/2008/10/unit-of-work-pattern-part-deux.html.
The second post goes into implementing a UnitOfWorkScope that allows sharing a unit of work transaction over multiple repositories. Hope that helps.
What visual studio theme are your code snippets in?
@Chris:
Here's the theme: http://www.codeinsanity.com/2009/04/visual-studio-theme.html
Post a Comment