The 'query' specification pattern
Hi, my name is David Vanderheyden. A dad, husband and .NET developer at Sweet Mustard with a passion for clean DDD software architecture. Like so many programmers, I love to challenge my beliefs and insights in software architecture and development to get a broader knowledge. In this article I’d like to share my insights and my interpretation with some examples of the Specification Pattern.
Let’s start by looking a bit closer at the Specification Pattern.
When building a Domain-Driven-Design solution, one of the most common discussions you’ll come across in the setup of a solution architecture is where to place querying, sorting and paging logic (QSP logic). Let’s be honest, this QSP logic is mostly defined by business or domain. Inside your Data Access Layer you don’t want to see any domain/business specific logic.
Wouldn’t it be nice to be able to keep your QSP logic outside your Data Access Layer? Wouldn’t it be awesome to have a completely loose coupled Data Access Layer that is nothing more than a clean connector to your database without any knowledge of the domain?
How is this attainable?
The 'query' specification pattern to the rescue
The solution for placing your QSP logic outside your Data Access Layer, or your preferred place of choice, is to use a Specification. In essence, the Specification design pattern is describing a query in an object. This Specification or ‘query (object)’ can be sent to a repository method that accepts an ISpecification and produces the expected result.
In my example, I provide the ISpecification with all the necessary properties to shape the query. This can be extended with an expression to project the query to a new object. However, in the current version of EntityFrameworkCore, this gives issues when projecting nested entities, causing N+1 queries. This issue should be resolved in EFCore 3, therefore I didn’t use projections in the demo code.
In the demo code I provided following properties to the ISpecification:
- Criteria: This reflects our ‘Where-clause’
- Includes: These are the includes we want to add to our model, this to avoid using lazy loading with our ORM (EntityFrameworkCore)
- Paging: IsPagingEnabled, Skip, Take are the properties used for applying paging to any query.
- OrderByExpressions: The orderby expressions for ordering in any direction. Multiple OrderBy expressions are executed in the same order as they appear in the collection.
The ISpecification looks like this:
The abstract implementation of the ISpecification, the BaseSpecification class, can be used to project or shape the resulting data.
The implemented properties are either private setters, that can be set by virtual functions to apply paging, add includes, and add ordering, or without a setter like the criteria. The criteria is set directly via the constructor.
The abstract BaseSpecification class looks like this:
As an example, I’ve created a CharactersOfTypeHumanSpecification class. With this Specification I want to reshape our query so that only characters of the type Human will be returned (in the demo code there are better ways for retrieving this, but for testing purposes it’s ideal.). The set criteria uses a LINQ-expression that eventually will reshape the query to this:
=> _context.Set().Where(c => c.GetType().Equals(typeof(Human)))
In the demo code I’m using EntityFrameworkCore as ORM. And I love the use of the Repository Pattern, but big applications tend to have a big number of repositories. The big benefit we enjoy by combining both the epository Pattern and the Specification Pattern is that we can create a generic repository that works in 99% of the cases. I strongly believe that there will always be cases that move beyond the code’s grasp, but hey… . I’ve been using this setup in 3 projects and I haven’t come across this problem yet.
Our generic repository eventually looks like this, where we use the ISpecification interface onto the GetAllAsync overload or the separate method GetSingleAsync().
The only thing missing to get it all full circle is adding an evaluator that applies the Specification to our query. By applying clean code principles, everything is created in small methods so that each method can be unit-tested. Indeed, everything can be unit tested, but I’ll showcase this later.
Notice that applying the Includes is done by a resolver. This is needed because EntityFrameworkCore has the Include/ThenInclude methods, which in our case is a bit more tricky to apply. What does work and uses the Include/ThenInclude methods under the hood as well, is using an include based on a path string. That’s basically what this resolver does: it transforms c => c.ParentEntity.Select(p => p.ChildEntity) to a clean path string “ParentEntity.ChildEntity”. You can take a closer look at the resolver in the DemoCode.
The biggest benefit we receive from the specification pattern is that it, as stated before, can be fully unit tested. In our example above with the CharactersOfTypeHumanSpecification I can unit-test this specification as followed:
In summary, this example is not in line with the composing Specifications (AND/OR/NOT) you’ll come across in a lot of articles. I love the way this setup works. Implementing new calls is achieved faster and everything is loosely coupled to the ORM project. Given the additional advantage that it only takes 2 classes to rework (the generic repository and the SpecificationEvaluator) to connect our logic with any other ORM.
This way of implementation is a personal intake and I’d love to hear your thoughts and opinions about it.
The source code contains more examples, if you’re interested.