Various topics from software architecture part 4: the unit of work continued

Introduction

In the previous post we looked at the basic goals and the abstractions associated with the unit of work pattern. We came to the conclusion that it might not be necessary to introduce an abstraction for the unit of work to begin with as modern ORMs like EntityFramework already have a well-tested unit of work object inside. There may still be cases though where you can make use of the pattern and the associated unit of work repository.

In this post we’ll see some possible mock implementations of these abstractions and how they can be wired up in the repository layer.

Implementation stubs

We’ll briefly look at some implementation skeletons for the IUnitOfWork and IUnitOfWorkRepository interfaces and how they can be used in a concrete repository.

Let’s implement IUnitOfWork first with some dictionaries that hold the changes to the domain objects – that is to simulate at a basic level what an EF object context would do:

public class BasicUnitOfWork : IUnitOfWork
{
	private Dictionary<IDomain, IUnitOfWorkRepository> _insertedAggregates;
	private Dictionary<IDomain, IUnitOfWorkRepository> _updatedAggregates;
	private Dictionary<IDomain, IUnitOfWorkRepository> _deletedAggregates;

        public BasicUnitOfWork()
	{
		_insertedAggregates = new Dictionary<IDomain, IUnitOfWorkRepository>();
		_updatedAggregates = new Dictionary<IDomain, IUnitOfWorkRepository>();
		_deletedAggregates = new Dictionary<IDomain, IUnitOfWorkRepository>();
	}

	public void RegisterUpdate(IDomain domain, IUnitOfWorkRepository repository)
	{
		if (!_updatedAggregates.ContainsKey(domain))
		{
			_updatedAggregates.Add(domain, repository);
		}
	}

	public void RegisterInsertion(IDomain domain, IUnitOfWorkRepository repository)
	{
		if (!_insertedAggregates.ContainsKey(domain))
		{
			_insertedAggregates.Add(domain, repository);
		}
	}

	public void RegisterDeletion(IDomain domain, IUnitOfWorkRepository repository)
	{
		if (!_deletedAggregates.ContainsKey(domain))
		{
			_deletedAggregates.Add(domain, repository);
		}
	}

	public void Commit()
	{
		foreach (IDomain domain in _insertedAggregates.Keys)
		{
			_insertedAggregates[domain].PersistInsertion(domain);
		}

		foreach (IDomain domain in _updatedAggregates.Keys)
		{
			_updatedAggregates[domain].PersistUpdate(domain);
		}

		foreach (IDomain domain in _deletedAggregates.Keys)
		{
			_deletedAggregates[domain].PersistDeletion(domain);
		}
	}
}

IUnitOfWorkRepository can be implemented by an abstract base RepositoryBase class in your concrete repository layer:

public abstract class RepositoryBase<DomainType, IdType> : IUnitOfWorkRepository where DomainType : IDomain
{
	private readonly IUnitOfWork _unitOfWork;

	public RepositoryBase(IUnitOfWork unitOfWork)
	{
		if (unitOfWork == null) throw new ArgumentNullException("Unit of work");
		_unitOfWork = unitOfWork;
	}

	public void Update(DomainType domain)
	{
		_unitOfWork.RegisterUpdate(domain, this);
	}

	public void Insert(DomainType domain)
	{
		_unitOfWork.RegisterInsertion(domain, this);
	}

	public void Delete(DomainType domain)
	{
		_unitOfWork.RegisterDeletion(domain, this);
	}

	public void PersistInsertion(IDomain domain)
	{
		//technology specific code to persist insertion
	}

	public void PersistUpdate(IDomain domain)
	{
		//technology specific code to persist update
	}

	public void PersistDeletion(IDomain domain)
	{
		//technology specific code to persist deletion
	}
}

Notice the “this” keywords how we send in the RepositoryBase object itself into the Register methods of the unit of work. As Repository implements IUnitOfWorkRepository that’s perfectly OK to do. Also, feel free to extend the Repository class if you need to pass in other dependencies. The examples you see here are only generic implementation stubs to illustrate the key concepts.

These operations are so common and repetitive that we can put them in this abstract base Repository class instead of letting the domain-specific repositories implement them over and over again. All they do is adding the operations to the queue of the unit of work to be performed when Commit() is called.

Upon Commit() the unit of work repository, i.e. the abstract RepositoryBase object will persist the changes using the in memory object context.

This model is relatively straightforward to change:

  • If you need a plain file-based storage mechanism then you might create a FileObjectContext class where you read to and from a file.
  • For EntityFramework and Linq to SQL you’ll use the built-in object context classes to keep track of and persist the changes
  • File-based NoSql solutions generally also have drivers for .NET – they can be used to implement a NoSql solution

So the possibilities are endless in fact. You can hide the implementation behind abstractions such as the IUnitOfWork interface or the Repository abstract class. It may well be that you need to add methods to the Repository class depending on the data storage technology you use but that’s perfectly acceptable. The concrete repository layer is…, well, concrete. You can dedicate it to a specific technology.

You can have several different implementations of the IUnitOfWork and IUnitOfWorkRepository interfaces and test them before you let your application go public. You can even mix and match the implementations:

  • Register the changes in a temporary file and commit them in NoSql
  • Register the changes in memory and commit them to the cache
  • Register the changes in cache and commit them to SQL Azure

Of course these combinations are very exotic but it shows you the flexibility behind all these abstractions. Don’t assume that the Repository class is a solution for ALL types of concrete unit of work. You’ll certainly have to modify it depending on the concrete data storage mechanism you work with. However, note the following points:

  • The rest of the application will not be concerned with the concrete data access implementation
  • The implementation details are well hidden behind this layer without them bubbling up to and permeating the other layers

So as long as the concrete implementations are hidden in the data access layer you’ll be fine.

The updated CustomerRepository

Recall from this post that we had the following plain customer repository class without any unit of work:

public class CustomerRepository : ICustomerRepository
{
	public Customer FindByName(string nameToSearch)
	{
		throw new NotImplementedException();
	}

	public Customer FindBy(int id)
	{
		throw new NotImplementedException();
	}

	public void Update(Customer aggregate)
	{
		throw new NotImplementedException();
	}

	public void Insert(Customer aggregate)
	{
		throw new NotImplementedException();
	}

	public void Delete(Customer aggregate)
	{
		throw new NotImplementedException();
	}
}

How can we make use of our abstract RepositoryBase class? It can derive from RepositoryBase and implement all the missing methods itself:

public class CustomerRepositoryWithUnitOfWork : RepositoryBase<Customer, int>, ICustomerRepository
{
	public CustomerRepositoryWithUnitOfWork(IUnitOfWork unitOfWork) : base(unitOfWork)
	{}

	public Customer FindBy(int id)
	{
		//concrete repository lookup implementation ignored
		throw new NotImplementedException();
	}

	public Customer FindByName(string nameToSearch)
	{
		//concrete repository lookup implementation ignored
		throw new NotImplementedException();
	}		
}

We derive from the abstract RepositoryBase class and declare that the object is represented by the Customer class in the domain layer and has an id type int. We inherit the FindBy and the FindByName methods. FindBy() is coming indirectly from the IRepository interface which in turn is implemented by the ICustomerRepository interface. And where are the other three methods of IRepository, i.e. Update, Insert and Delete? Remember, that the Update, Insert and Delete methods have already been implemented in the Repository class so we don’t need to worry about them. Any time we create a new domain-specific repository, those methods have been taken care of.

Calling the repositories

It’s also interesting how another layer, like the application service could communicate with the data store through all these abstractions. We’ve seen an example of that in the series on DDD. The following posts are the most relevant:

The concrete service implementations will need the abstract repository and the unit of work as dependencies:

private readonly ICustomerRepository _customerRepository;
private readonly IUnitOfWork _unitOfWork;
 
public CustomerService(ICustomerRepository customerRepository, IUnitOfWork unitOfWork)
{
    if (customerRepository == null) throw new ArgumentNullException("Customer repo");
    if (unitOfWork == null) throw new ArgumentNullException("Unit of work");
    _customerRepository = customerRepository;
    _unitOfWork = unitOfWork;
}

Here’s an example of using the repository to find a customer. I’ve only kept the relevant bits of code:

public GetCustomerResponse GetCustomer(GetCustomerRequest getCustomerRequest)
{

    Customer customer = null;
    try
    {
        customer = _customerRepository.FindBy(getCustomerRequest.Id);
        ...
    }
    catch (Exception ex)
    {
        ...
    }
}

And here’s an example showing the unit of work in use:

public InsertCustomerResponse InsertCustomer(InsertCustomerRequest insertCustomerRequest)
{
    Customer newCustomer = ...
    try
    {
        _customerRepository.Insert(newCustomer);                
        _unitOfWork.Commit();
    }
    catch (Exception ex)
    {
        ...
    }
}

That’s about it as far as the unit of work pattern is concerned. In the next post which finishes this series we’ll discuss the notion of an aggregate root.

View the list of posts on Architecture and Patterns here.

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

2 Responses to Various topics from software architecture part 4: the unit of work continued

  1. Pingback: Architecture and patterns | Michael's Excerpts

  2. linkmydata says:

    An excellent series of articles; particularly in that you’ve presented one of the better designs of this pattern. There are many tutorials online that incorrectly couple repositories in the unit of work, for example. Thanks for posting.

Leave a comment

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.