Specification pattern: C# implementation
The topic described in this article is a part of my Specification Pattern in C# Pluralsight course.
Specification pattern is not a new topic, there are many of its implementations on the Internet already. In this post, I’d like to discuss the use cases for the pattern and compare several common implementations to each other.
1. Specification pattern: what’s that?
Specification pattern is a pattern that allows us to encapsulate some piece of domain knowledge into a single unit - specification - and reuse it in different parts of the code base.
Use cases for this pattern are best expressed with an example. Let’s say we have the following class in our domain model:
public class Movie : Entity
{
public string Name { get; }
public DateTime ReleaseDate { get; }
public MpaaRating MpaaRating { get; }
public string Genre { get; }
public double Rating { get; }
}
public enum MpaaRating
{
G,
PG13,
R
}
Now, let’s assume that users want to find some relatively new movies to watch. To implement this, we can add a method to a repository class, like this:
public class MovieRepository
{
public IReadOnlyList<Movie> GetByReleaseDate(DateTime minReleaseDate)
{
/* ... */
}
}
If we need to search by rating or genre, we can introduce other methods as well:
public class MovieRepository
{
public IReadOnlyList<Movie> GetByReleaseDate(DateTime maxReleaseDate) { }
public IReadOnlyList<Movie> GetByRating(double minRating) { }
public IReadOnlyList<Movie> GetByGenre(string genre) { }
}
Things get a bit more complicated when we decide to combine the search criteria, but we are still in a good shape. We can introduce a single Find method which would handle all possible criteria and return a consolidated search result:
public class MovieRepository
{
public IReadOnlyList<Movie> Find(
DateTime? maxReleaseDate = null,
double minRating = 0,
string genre = null)
{
/* ... */
}
}
And of course, we can always add other criteria to the method as well.
Problems arise when we need to not only search for the data in the database but also validate it in the memory. For example, we might want to check that a certain movie is eligible for children before we sell a ticket to it, so we introduce a validation, like this:
public Result BuyChildTicket(int movieId)
{
Movie movie = _repository.GetById(movieId);
if (movie.MpaaRating != MpaaRating.G)
return Error("The movie is not eligible for children");
return Ok();
}
If we also need to look into the database for all movies that meet the same criterion, we have to introduce a method similar to the following:
public class MovieRepository
{
public IReadOnlyList<Movie> FindMoviesForChildren()
{
return db
.Where(x => x.MpaaRating == MpaaRating.G)
.ToList();
}
}
The issue with this code is that it violates the DRY principle as the domain knowledge about what to consider a kids movie is now spread across 2 locations: the BuyChildTicket method and MovieRepository. That is where the Specification pattern can help us. We can introduce a new class which knows exactly how to distinguish different kinds of movies. We then can reuse this class in both scenarios:
public Result BuyChildTicket(int movieId)
{
Movie movie = _repository.GetById(movieId);
var spec = new MovieForKidsSpecification();
if (!spec.IsSatisfiedBy(movie))
return Error("The movie is not eligible for children");
return Ok();
}
public class MovieRepository
{
public IReadOnlyList<Movie> Find(Specification<Movie> specification)
{
/* ... */
}
}
Not only does this approach removes domain knowledge duplication, it also allows for combining multiple specifications. That, in turn, helps us easily set up quite complex search and validation criteria.
There are 3 main use cases for the Specification pattern:
-
Looking up data in the database. That is finding records that match the specification we have in hand.
-
Validating objects in the memory. In other words, checking that an object we retrieved or created fits the spec.
-
Creating a new instance that matches the criteria. This is useful in scenarios where you don’t care about the actual content of the instances, but still need it to have certain attributes.
We will discuss the first 2 use cases as they are the most common in my experience.
2. Naive implementation
We’ll start implementing the specification pattern with a naive version first and will then move forward to a better one.
The first solution that comes to mind when you face the problem described above is to use C# expressions. To a great extent, they themselves are an implementation of the specification pattern. We can easily define one in code and use it in both scenarios, like this:
// Controller
public void SomeMethod()
{
Expression<Func<Movie, bool>> expression = m => m.MpaaRating == MpaaRating.G;
bool isOk = expression.Compile().Invoke(movie); // Exercising a single movie
var movies = _repository.Find(expression); // Getting a list of movies
}
// Repository
public IReadOnlyList<Movie> Find(Expression<Func<Movie, bool>> expression)
{
return db
.Where(expression)
.ToList();
}
The problem with this approach, however, is that while we do gather the domain knowledge regarding how to categorize kids movies in a single place (expression variable in our case), the abstraction we’ve chosen isn’t a good fit. Variables are by no means a suitable place for such important information. The domain knowledge represented in such a way is hard to reuse and tends to be duplicated across the whole application because of that. Ultimately, we end up with the same issue we started off with.
A variation of this naive implementation is introducing a generic specification class:
public class GenericSpecification<T>
{
public Expression<Func<T , bool>> Expression { get; }
public GenericSpecification(Expression<Func<T , bool>> expression)
{
Expression = expression;
}
public bool IsSatisfiedBy(T entity)
{
return Expression.Compile().Invoke(entity);
}
}
// Controller
public void SomeMethod()
{
var specification = new GenericSpecification<Movie>(
m => m.MpaaRating == MpaaRating.G);
bool isOk = specification.IsSatisfiedBy(movie); // Exercising a single movie
var movies = _repository.Find(specification); // Getting a list of movies
}
// Repository
public IReadOnlyList<Movie> Find(GenericSpecification<Movie> specification)
{
return db
.Where(specification.Expression)
.ToList();
}
This version has essentially the same drawbacks, the only difference is that here we have a wrapper class on top of the expression. Still, in order to reuse such specification properly, we have to create a single instance of it once and then share this instance across our code base somehow. This design doesn’t help much with DRY.
That leads us to an important conclusion: generic specifications are a bad practice. If a specification allows you to indicate an arbitrary condition, it becomes just a container for the information which is passed to it by its client and doesn’t solve the underlying problem of domain knowledge encapsulation. Such specifications simply don’t contain any knowledge themselves.
3. Strongly-typed specifications
So how can we overcome the problem? The solution here is to use strongly-typed specifications. That is specifications in which we hard code the domain knowledge, with little or no possibility to alter it from the outside.
Here’s how we can implement it in practice:
public abstract class Specification<T>
{
public abstract Expression<Func<T , bool>> ToExpression();
public bool IsSatisfiedBy(T entity)
{
Func<T , bool> predicate = ToExpression().Compile();
return predicate(entity);
}
}
public class MpaaRatingAtMostSpecification : Specification<Movie>
{
private readonly MpaaRating_rating;
public MpaaRatingAtMostSpecification(MpaaRating rating)
{
_rating = rating;
}
public override Expression<Func<Movie, bool>> ToExpression()
{
return movie => movie.MpaaRating <= _rating;
}
}
// Controller
public void SomeMethod()
{
var gRating = new MpaaRatingAtMostSpecification(MpaaRating.G);
bool isOk = gRating.IsSatisfiedBy(movie); // Exercising a single movie
IReadOnlyList<Movie> movies = repository.Find(gRating); // Getting a list of movies
}
// Repository
public IReadOnlyList<T> Find(Specification<T> specification)
{
using (ISession session = SessionFactory.OpenSession())
{
return session.Query<T>()
.Where(specification.ToExpression())
.ToList();
}
}
With this approach, we lift the domain knowledge to the class level making it much easier to reuse. No need to keep track of spec instances anymore: creating additional specification objects doesn’t lead to the domain knowledge duplication, so we can do it freely.
Also, it’s really easy to combine the specifications using And, Or, and Not methods. Here’s how we can do that:
public abstract class Specification<T>
{
public Specification<T> And(Specification<T> specification)
{
return new AndSpecification<T>(this, specification);
}
// And also Or and Not methods
}
public class AndSpecification<T> : Specification<T>
{
private readonly Specification<T> _left;
private readonly Specification<T> _right;
public AndSpecification(Specification<T> left, Specification<T> right)
{
_right = right;
_left = left;
}
public override Expression<Func<T , bool>> ToExpression()
{
Expression<Func<T , bool>> leftExpression = _left.ToExpression();
Expression<Func<T , bool>> rightExpression = _right.ToExpression();
BinaryExpression andExpression = Expression.AndAlso(
leftExpression.Body, rightExpression.Body);
return Expression.Lambda<Func<T , bool>>(
andExpression, leftExpression.Parameters.Single());
}
}
var gRating = new MpaaRatingAtMostSpecification(MpaaRating.G);
var goodMovie = new GoodMovieSpecification();
var repository = new MovieRepository();
IReadOnlyList<Movie> movies = repository.Find(gRating.And(goodMovie));
You can find the full source code and usage examples on Github.
4. Returning IQueryable<T>
from a repository
A question that is somewhat related to the specification pattern is: can repositories just return an IQueryable<T>
? Wouldn’t it be easier to allow clients to query data from the backing store the way they want? For example, we could add a method to the repository like this:
// Repository
public IQueryable<T> Find()
{
return session.Query<T>();
}
And then use it in a controller specifying the actual criteria ad hoc:
// Controller
public void SomeMethod()
{
List<Movie> movies = _repository.Find()
.Where(movie => movie.MpaaRating == MpaaRating.G)
.ToList();
}
This approach has essentially the same drawback as our initial specification pattern implementation: it encourages us to violate the DRY principle by duplicating the domain knowledge. This technique doesn’t offer us anything in terms of consolidating it in a single place.
The second drawback here is that we are getting database notions leaking out of repositories. The implementation of IQueryable<T>
highly depends on what LINQ provider is used behind the scene, so the client code should be aware that there potentially are queries which can’t be compiled into SQL.
And finally, we are also getting a potential LSP violation. IQueryable
s are evaluated lazily, so we need to keep the underlying connection opened during the whole business transaction. Otherwise, the method will blow up with an exception. By the way, an implementation with IEnumerable
s have essentially the same problem, so the best way to overcome this issue is to return IReadOnlyList
or IReadOnlyCollection
interfaces.
6. Summary
-
Don’t use C# expressions as a Specification pattern implementation, they don’t allow you to actually gather the domain knowledge into a single authoritative source.
-
Don’t return
IQueryable<T>
from repositories, it brings up issues with DRY and LSP violation and leaking database concerns to the application logic.
- ← Classes internal to an aggregate: entities or value objects?
- 3 ways of achieving code correctness →
Subscribe
Comments
comments powered by Disqus