After Thought

Unit of Work Part 1

with 4 comments

Quoting from Martin Fowler’s Patterns of Enterprise Application Architecture, “Unit of Work maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems”.

This post delves into the details and implementation of Unit of Work based on Martin Fowler’s explanation of Unit of Work in his book. First, we will try to understand the Unit of Work concept, then implement a Unit of Work and finally, we will see how it can be effectively used in a sample application.

An application request can be atomic (affecting single business object) or composite in nature (affecting multiple business objects). It becomes even more tricky when a database has to be updated with these changes. If you imagine a scenario where a request updates multiple business objects, then the following questions arise.

  • Do you make multiple calls to the database to save each of the objects  ?
  • You save all of them together into the database ?

The main drawback of the former approach is that your request handler has to be database aware. It has to make multiple calls to database for each and every request and if there is any error in the process, rollback the changes. Other issues are having to keep the transaction open throughout the request and maintain referential integrity when saving the objects into the database.

The latter approach keeps track of all the business objects that were changed during a request and at the end of the request all the business objects that were changed are saved into the database. This may involve opening a transaction, committing the changes and then closing the transaction. For this approach to work, it needs someone to track the objects that were changed, managing transaction and committing the changes. This is exactly what Unit of Work is intended to do.

Unit of Work keeps track of all the objects that are changed during a request, open a transaction, save the business objects in to the database and then close the transaction. In case of error, rollback all the changes. By doing this, it also accomplishes another important aspect of development - separation of concern which will be discussed in detail in part 2. Thus, the developer need not worry about persisting business objects into the database or dealing with database calls.

Before we go and implement our own Unit Of Work, let us see if there is already something out there that we can use. ISession in NHibernate represents the conversation between application and the database. ISession is just a container for Unit of Work that keeps track of objects that are changed during a request.

To leverage the Unit of Work in ISession, let us go ahead and implement ISessionContainer that wraps an ISession.

public interface ISessionContainer : IDisposable
{
	void Commit();
	ISession CurrentSession { get; }
}

public class SessionContainer : ISessionContainer
{
	private readonly ITransaction _transaction;
	private bool _isDisposed;

	public SessionContainer(ISessionFactory sessionFactory)
	{
		CurrentSession = sessionFactory.OpenSession();
		_transaction = CurrentSession.BeginTransaction();
	}

	public void Commit()
	{
		if (_isDisposed)
		{
			throw new InvalidOperationException("Could not commit as Unit of Work was not initialized");
		}
		_transaction.Commit();
	}

	public ISession CurrentSession { get; private set; }

	public void Dispose()
	{
		if (_isDisposed) return;
		_transaction.Dispose();
		CurrentSession.Dispose();
		_isDisposed = true;
	}
}

Creating a new instance of SessionContainer opens a new Session and starts a new transaction. It also has a Commit method that saves all the changes to the database and closes the transaction. Dispose method closes the transaction if not already closed and then closes the Session.

The next step is to delegate persistence management to SessionContainer. Let us imagine a situation where we want to update Book details that is still in works with the newly recruited high profile Author and update the projected price of the book. Before we proceed with the example here are the details of the Book and Author and their properties.

public class Book : DomainEntity<Book>
{
        public Book()
	{
		Authors = new List<Author>();
	}

	public virtual string ISBN { get; set; }
	public virtual string Title { get; set; }
	public virtual decimal Price { get; set; }

	public virtual IList<Author> Authors { get; private set; }
	public virtual void AddAuthor(Author author)
	{
		if (!Authors.Contains(author))
		{
			author.AddBook(this);
			Authors.Add(author);
		}
	}
}

public class Author : DomainEntity<Author>
{
	public Author()
	{
		Books = new List<Book>();
	}

	public virtual string Name { get; set; }

	public virtual IList<Book> Books { get; private set; }
	public virtual void AddBook(Book book) { ... }
}
public class Author : DomainEntity<Author>
{
public Author()
{
Books = new List<Book>();
Publishers = new List<Publisher>();
}
public virtual string Name { get; set; }
public virtual IList<Book> Books { get; private set; }
public virtual IList<Publisher> Publishers { get; private set; }
public virtual void AddBook(Book book)
{
if (Books.Contains(book))
return;
book.AddAuthor(this);
Books.Add(book);
}
public virtual void AddPublisher(Publisher publisher)
{
if (!Publishers.Contains(publisher))
{
publisher.AddAuthor(this);
Publishers.Add(publisher);
}
}
}

UpdateBook is a method (imagine an action method in a MVC Controller) that adds the new author to the Book and updates the price.

public class BookService : IService
{
    private readonly IUnitOfWork _sessionContainer;
    private readonly IRepository _repository;

    public BookService(IUnitOfWork unitOfWork, IRepository repository)
    {
        _sessionContainer = unitOfWork;
        _repository = repository;
    }

    public Book UpdateBook(Book book, Author author)
    {
        try
        {
            book.AddAuthor(author);
            book.Price = 100m;
            _repository.Save(book);
            _sessionContainer.Commit();
        }
        finally
        {
            _sessionContainer.Dispose();
        }
        return book;
    }
}

In the UpdateBook method, we add the newly recruited author and updated the price to $100.  We used the SessionContainer that we created in the previous post to commit the changes at the end of the method and dispose the transaction. If there is any error while committing the changes, a call to Dispose() method (as the control always ends up in the finally block) ensures that the transaction is disposed and in the process all the unsaved changes will be lost.

Only hiccup in the above solution is that you will have to inject ISessionContainer into all the classes that deal with repository. Furthermore, you will have to repeat the same lines of code to commit the changes and dispose the transaction object in all the methods that update the repository. This puts the onus on developer not to forget to add these lines of code in all the methods.

The next step would be to isolate this responsibility so that developer doesn’t have to deal with committing the changes and disposing the transaction.  In the next post we will implement a small console application that tackles this issue. Since Web especially MVC frameworks (ASP.net and Fubu) are so relevant today, we will also discuss how these frameworks handle this seperation of concern.

About these ads

Written by shashankshetty

October 28, 2009 at 10:11 pm

4 Responses

Subscribe to comments with RSS.

  1. When implementing a Unit of Work, don’t you think the save on the repository should be removed? Even I have implemented the Unit of Work as you have shown here, but now I feel that the repository should not have the responsibility of saving the domain object.

    Vinay

    October 29, 2009 at 11:28 pm

    • Vinay,

      If repository is not responsible for saving the domain or dirty objects, then you will have to move that onus to either domain saving itself (this means the domain objects are doing more than they may need to do) or the other option would be to keep a list of all the dirty objects and save the list of dirty objects in the RequestHandler. (I will have to think about this option). What is your thought on who should be responsible for saving the domain.

      shashankshetty

      November 4, 2009 at 11:34 am

  2. From what I understand (now), repositories are in memory collection of your domain objects and I feel that persistence concern shouldn’t be the responsibility of the repository. Unit of Work should be tracking the dirty objects. When you call a commit on the Unit of Work, then the Unit of Work should persist the dirty objects to the database, don’t you think?

    Vinay

    November 4, 2009 at 10:36 pm

    • One thing to note here is we are not implementing a pure Unit of Work, as ISession is doing the tracking of dirty objects for us. So the only option left would be to save the dirty objects in the Request Handler as you suggested. (that is the reason I mentioned doing that in Request Handler in my previous comment). But doing that in Request Handler would not be straight forward as we may need more information to do this. I can talk to couple other people in the next couple weeks and see what they think about this.

      shashankshetty

      November 5, 2009 at 10:29 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: