23. 7. 2010

Specification pattern and complex queries over a repository

In the last post about repository pattern we have discussed the repository interface and it's usage. We now know that the repository acts as in-memory collection for the client and that it hides the complexity of loading and writing data to the persistent storage.

Our first simple prototype of the interface looked like this:

public interface IPostRepository
{
  void Insert(Post post);
  Post GetById(int id);
  void Update(Post post);
  void Delete(Post post);
}

But what about those complex queries we need to deal with? The solution for this problem in DDD manner is Specification pattern.

Specification pattern allows the client to specify which domain entities it wants. Usually from the repository for the entity. Specification pattern is domain pattern, that means the methods names and input/output types are strong with respect to domain and entities. In comparing to query pattern that is generalization of the specification pattern and belongs to category of generic design patterns. It is a good practice to implement it using fluent interface pattern.

Lets have a look on following example:

// domain entity
public class Post
{
  public virtual int Id { get; set; }
  public virtual string Title { get; set; }
  public virtual string Text { get; set; }
  public virtual DateTime Published { get; set; }
  IList<Comment> Comments { get; set; }
}

We have requirements from our functional specification from the customer that he wants to display all posts between certain dates. In other words, we wants Posts than were published after certain date and before certain date. Remember the later sentence and how it will nicely map to our fluent specification interface. This is also important that the business buys and developers share the same language to better understand each other.

Naive and wrong approach would be to extend the interface with a method that accepts two nullable DateTime parameters and returns back list of posts. This is what you will actually see in many examples and tutorials accross the internet these days. Again - don't do it, it is wrong. It is going against object oriented design (OOD) and SOLID principles.

What is wrong with naive approach. First of all, separation of concerns. You don't want to have dozens of methods in the interface for every single use case. Think about usage of such interface, about users of such interface. They would need to implement every single method. And not saying word about testing. In general, repository acts as in-memory collection of your entities, it should not have idea about the queries, about the business requirements or more correctly specifications you would like to do with it. Each specification should resides in a dedicated class, effectively follow Command pattern and all its benefits, like dependency injection. Why the heck you would change working repository implementation in order to implement new query? It just smells bad and it is really not natural.

Correct solution
The one of the right way to do it is make use of Service locator (or dependency injection) and Command patterns. Of course we have to extend the repository interface but this extension is one single method that provides the client with specifications that can filter the requested entities from the repository. Specifying what we want is done via Specification pattern.

On interface level we provide extension point as following:

public interface IPostRepository
{
  ...
 
  // extension point to get specification
  TSpecification Specify<TSpecification>() where TSpecification : class
}

The above method uses underlying service locator to locate configured specification in service locator (DI/IoC container). We have use of generics here so we get strongly typed result back. Lets have a look how such simple specification that uses fluent interface pattern could look like:

public interface IPostSpecification
{
  IPostSpecification PostsAfter(DateTime dateTime);
  IPostSpecification PostsBefore(DateTime dateTime);
  IList<Post> ToList();
}


Note that all methods contain strong domain oriented names. And return the same type, this is called fluent interface. Methods that finalizes the chain of specifications is ToList().

Connecting all interfaces together we get following usage:

// assume that some posts are already in the repository
var postRepository = IoC.Resolve<IPostRepository>();

var posts = postRepository.Specify<IPostSpecification>()
          .PostsAfter(DateTime.Now.AddDays(-100))
          .PostsBefore(DateTime.Now.AddDays(-50))
          .ToList();

We have used fluent interface to specify what we want and asked for the list of results. Note that the fluent interface allows us to specify just one date, or no date at all. The later would result in the list with all posts.

Also it is important to realize that we don't have to put all use cases into one specification. We can have as many specification interfaces as we like. Rememer divide and conquer paradigm? Here we are making use of it.

Now if we want to add new specification to our application, we won't touch the repository at all! We implement new interface, provide implementation with unit tests and configure it in service locator (IoC container). That's it. All is clean, well separated and easy to test.

Initialization of specification
Hawkeyed readers spotted it already. How is specification connected to repository. We can specify our requirements in nice fluent way but what is making the connection? Of course the specification needs to be initialized. This is done in the repository itself. When it resolves the specification from the service locator, the repository should intialize the specification with something that connects it with the collection where the entities are stored. This can be directly the repository itself, or the underlying unit of work instance.

Best way to do it is to provide specification interface with an Initialize method that takes unit of work as an argument. Doing it other way round is will work but it is not that flexible. In our simple example we will provide to the specification whole repository and leave the responsibility of the initialization to the specification itself. The specification then have to cast the repository to concrete type (e.g. NHibernatePostRepository and make use of ISession) and access the unit of work instance to filter requested entities.

So it will look like this:

public interface IPostSpecification
{
  ...
  void Initialize(IPostRepository repository);
}


The concrete implementation of specification is tightly coupled to concrete implementation of repository but this natural and ok. If possible, it can be improved with IQueryable interface. As usual the correct behaviour and configuration will be unsured by unit tests.

For generic implementation and example, make sure you check out generic repository project. Especially interfaces like ISpecification, ISpecificationResult and ISpecificationLocator.

SpecificationResult
Sooner or later when you start to using this approach, you will find out that some functionalities are repeating between specifications. Methods like ToList(), Single(), Take(int count), Skip(int count) will appear basically in all specifications. Remember don't repeat yourself (DRY) principle. The solution is easy and we need to pack the common functionality into the interface I call ISpecificationResult. The interfaces will change like this:

public interface IPostSpecification
{
  ...
  IPostSpecificationResult ToResult(); // replaces ToList and all common methods
}


public interface IPostSpecificationResult
{
  IPostSpecificationResult Take(int count);
  IPostSpecificationResult Skip(int count);
 
  IList<Post> ToList();
  Post Single();
}


Again you can see we are making use of fluent interface. Now all specification related to Posts can concentrate on the business needs and requirements and common functionality is clearly separated.

All of the above can be implemented in a generic way. The open source generic repository projects is aiming to provide you with all necessary interface and base implementations so you can focus just on your domain and the business rules. It is worth to check it out if you are looking for robust and clean design of your application.

1 komentár:

  1. Can you provide some samles Generic Repository + Autofac container, Please.
    How I can register and resolve specificationLocator in autofac container.
    Thank you! Great work!

    OdpovedaťOdstrániť