After Thought

Archive for October 2009

Unit of Work Part 2

leave a comment »

In the previous post we started off with a brief discussion on Unit of Work and its implementation. We also implemented  ISessionContainer that is a wrapper for ISession and used it to persist changes into the database. In this post let us see if we can isolate the below piece of code that has to be repeated in all the methods that deals with repository.

try
{
	// do work
	_repository.Save(Entity);
	_sessionContainer.Commit();
}
finally
{
	_sessionContainer.Dispose();
}

Following implementation is inspired by Fubu Behaviors concept. Imagine every call from the client or web as a request and this request has to pass through a Request handler to reach our business logic. Let us create an interface IRequestHandler that would attempt to encapsulate a Request.

public interface IRequestHandler
{
  TOutput ProcessRequest<TInput1, TInput2, TOutput>(TInput1 input1, TInput2 input2, Func<TInput1, TInput2, TOutput> func)
    where TInput1 : class
    where TInput2 : class
    where TOutput : class;
}

The above interface takes in two input entities and a func that process these input entities and returns an output entity. Now let us go ahead and implement it.

public class AtomicRequestHandler : IRequestHandler
{
	private readonly ISessionContainer _sessionContainer;

	public AtomicRequestBehavior(ISessionContainer sessionContainer)
	{
		_sessionContainer = sessionContainer;
	}

	public TOutput ProcessRequest<TInput1, TInput2, TOutput>(TInput1 input1, TInput2 input2, Func<TInput1, TInput2, TOutput> func)
		where TInput1 : class
		where TInput2 : class
		where TOutput : class
	{
		try
		{
			var output = func.Invoke(input1, input2);

			_sessionContainer.Commit();

			return output;
		}
		finally
		{
			_sessionContainer.Dispose();
		}
	}
}

The name AtomicRequestHandler (name is subject to change upon a better name suggestion) means Handler for one request (imagine a web request). Initialized SessionContainer gets injected to AtomicRequestHandler.The ProcessRequest method invokes the method that has to be executed which in this case is UpdateBook. Upon completion of execution of UpdateBook method, it commits the transaction and closes the session. In case of any error anywhere in the execution, the transaction and session are closed throwing away all the unsaved changes.

Now let us re-implement the UpdateBook method with AtomicRequestHandler in mind.

public class SampleService : ISampleService
{
   private readonly IRepository _repository;

   public SampleService(IRepository repository)
   {
      _repository = repository;
   }

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

The above illustration has no sign of SessionContainer at all. The reason we were able to separate the code so easily is because of Inversion of Control.

StructureMap is IoC of choice in my implementations, but you can choose to use your favorite IoC.  The following code shows how all this is magically constructed.

ISampleService sampleService = ObjectFactory.GetInstance<ISampleService>();
IRequestHandler atomicRequestHandler = ObjectFactory.GetInstance<IRequestHandler>();
Book result = atomicRequestHandler.ProcessRequest<Book, Author, Book>(GetBook(), GetAuthor(), sampleService.UpdateBook)

The above code calls atomicRequestHandler.ProcessRequest method with input entities – Book and Author and sampleService.UpdateBook method as a delegate that makes sure that the UpdateBook method is executed within the transaction boundary.

This is exactly how Fubu makes sure your web Request is executed within a transaction boundary. The Handle Request method in ActionHttpHandler redirects request to the action method through a behavior similar to AtomicRequestHandler.

You can implement this in ASP.net MVC by writing your own CustomHttpModule that initializes a new SessionContainer on BeginRequest and disposes it on EndRequest. You can alternatively do this in Application_BeginRequest and Application_EndRequest in Global.asax.cs file. ActionFilter is another place where you can stick in unit of work, but it is generally not recommended as you will have to do this for every single action.

If you compare them, Fubu does it the best when compared to ASP.Net Mvc, because of its architectural preference of Composition over Inheritance.

Finally, I wouldn’t have been able to write this exhaustive analysis on Unit of Work if not for multiple discussions with Chad Myers, Josh Flanagan and Weston Binford. Download the sample application on which the examples discussed above are based on.

Advertisements

Written by shashankshetty

October 29, 2009 at 10:33 pm

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(&quot;Could not commit as Unit of Work was not initialized&quot;);
		}
		_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.

Written by shashankshetty

October 28, 2009 at 10:11 pm