The Specification Pattern


What is a Specification?

One of my favorite software development patterns is the specification pattern. This pattern provides a way of encapsulating business logic and helps to enforce the DRY principle.

As always with such things, a contrived example helps articulate the concept…

In a commerce system there are visitors and the business defines an active visitor as having used the system within the last 7 days and having placed an order within the last 30 days.

The specific logic is arbitrary. What is important is that we need to be able to define active visitors in numerous locations throughout our application and, should the business change their definition of an active visitor, we should be able to reflect the change in a single place within our code.

Enter the specification pattern which can be represented by the following interface:

public interface ISpecification<in T>
{
    bool IsSatisfiedBy(T subject)
}

Assuming we have services for checking system usage and order history we can implement this interface for our visitor example.

public class ActiveVisitorSpecification : ISpecification<Visitor>
{
    protected IUsageService Usage { get; set; }
    protected IOrderService Orders { get; set; }

    public ActiveVisitorSpecification(IUsageService usage, IOrderService orders)
    {
        Usage = usage;
        Orders = orders;
    }

    public bool IsSatisfiedBy(Visitor subject)
    {
        if(Usage.HasUsedInLastSevenDays(subject) && Orders.LatestOrderDaysAgo(subject) <= 30)
        {
            return true;
        }

        return false;
    }
}

As you can see from the example, we have encapsulated all the business logic within a single place and can now use this class throughout our application whenever we need to check if a visitor can be considered as being active.

// Get visitor
var customers = context.Visitors.Find(id);

// Initialize specification
var specification = new ActiveVisitorSpecification(usageService, orderService);

// Apply specification
if(specification.IsSatisfiedBy(c))
{
    // Do something with active visitor
}

Chaining multiple specifications

Whilst it’s great to encapsulate the business logic for active visitors within a single class, we might have been a little hasty. We didn’t stop to consider that the business has business logic to define other visitor groups such as recent visitors and recent customers.

The business defines a recent visitor as having used the system within the last 7 days and a recent customer as having placed an order within the last 30 days.

We can create a specification for each of these examples but now we are duplicating business logic across specifications instead. Still better than across the entire application but not a great improvement from what we originally had.

Instead we can use chaining to create discrete specifications for each of the specific examples and chain them together for the original definition of an active visitor being both a recent visitor and a recent customer.

Chaining is achieved by creating specifications that can apply boolean logic to other specifications. They implement the IsSatisfiedBy method just as before but their implementation is based on && (and), || (or), or ! (not) logic.

AndSpecification

public class AndSpecification<T> : ISpecification<T>
{
    protected ISpecification<T> Left { get; set; }
    protected ISpecification<T> Right { get; set; }

    public AndSpecification(ISpecification<T> left, ISpecification<T> right)
    {
        Left = left;
        Right = right;
    }

    public bool IsSatisfiedBy(T subject)
    {
        return Left.IsSatisfiedBy(subject) && Right.IsSatisfiedBy(subject);
    }
}

As you can see above, the implementation is very straightforward. It just checks if both the left AND right specifications are satisfied by the subject.

OrSpecification

public class OrSpecification<T> : ISpecification<T>
{
    protected ISpecification<T> Left { get; set; }
    protected ISpecification<T> Right { get; set; }

    public OrSpecification(ISpecification<T> left, ISpecification<T> right)
    {
        Left = left;
        Right = right;
    }

    public bool IsSatisfiedBy(T subject)
    {
        return Left.IsSatisfiedBy(subject) || Right.IsSatisfiedBy(subject);
    }
}

Once again, the implementation is very straightforward. It just checks if either the left OR right specifications are satisfied by the subject.

NotSpecification

public class NotSpecification<T> : ISpecification<T>
{
    protected ISpecification<T> Not { get; set; }

    public NotSpecification(ISpecification<T> not)
    {
        Not = not;
    }

    public bool IsSatisfiedBy(T subject)
    {
        return !(Not.IsSatisfiedBy(subject));
    }
}

This one is a little different as it simply checks that the specification is NOT satisfied by the subject.

Syntactic Sugar

Whilst this provides us a mechanism to chain multiple specifications together to create more complicated ones it isn’t pretty to instantiate these specifications – especially when nesting them within each other.

We can use static extension methods to add some syntactic sugar and make these really easy to use.

For example, for the AndSpecification we could have the following extension method:

public static ISpecification<T> And<T>(this ISpecification<T> left, ISpecification<T> right)
{
    return new AndSpecification<T>(left, right);
}

Now, we can replace our original ActiveVisitorSpecification with the following:

var recentVisitor = new RecentVisitor(usageService);
var recentCustomer = new RecentCustomer(orderService);
...
var activeVisitor = recentVisitor.And(recentCustomer);

We can even take it a step further and expose composite specifications (specification that are constructed by chaining others) within static methods so that we don’t duplicate the chaining in multiple places.

As with any pattern, the application of it depends on the individual developer.

Linq and IQueryable

The observant among you will have noticed that, whilst this is fine when we are dealing with individual customers, there may be times when we want to use a specification as a predicate for multiple customers.

This can be achieved by extending our original ISpecification<in T> interface.

public interface IWhereSpecification<T> : ISpecification<T>
{
    Expression<Func<T, bool>> Predicate { get; }
    IQueryable<T> SatisfiesMany(IQueryable<T> queryable);
}

A Predicate property exposes an expression that describes the specification using a predicate function whilst a new SatisfiesMany method takes an IQueryable<T> and returns an IQueryable<T> after having applied the specification.

Below is an abstract implementation of this interface:

public abstract class WhereSpecification<T> : IWhereSpecification<T>
{
    private Func<T, bool> _compiledExpression;
    private Func<T, bool> CompiledExpression { get { return _compiledExpression ?? (_compiledExpression = Predicate.Compile()); } }
    public Expression<Func<T, bool>> Predicate { get; protected set; }

    public bool IsSatisfiedBy(T subject)
    {
        return CompiledExpression(subject);
    }

    public virtual IQueryable<T> SatisfiesMany(IQueryable<T> queryable)
    {
        return queryable.Where(Predicate);
    }
}

Any subsequent concrete implementations can simply set the Predicate property.

We can also chain the specifications together as before by using a BinaryExpression when defining the subsequent predicate.

Now, when dealing with an IQueryable<T> you can reduce it using a specification and if you are using LINQ to SQL (e.g. Entity Framework) then the expression will be converted to a query meaning that you are only requesting a subset of data from SQL instead of requesting everything and reducing it in-memory.

Download from NuGet

If you want to use an existing implementation of the specification pattern you can do so by adding the Vouzamo.Specification NuGet package to your project.

Additionally, you can review the source on GitHub.

Advertisements

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