Specification Pattern vs Always-Valid Domain Model

There’s an interesting controversy between two DDD topics: the Specification pattern and the Always-Valid domain model.

1. Specification pattern

I’ve written multiple articles about the Specification pattern, including another controversy: Specification vs CQRS patterns.

This time, we’ll look at the contradiction the Specification pattern has with another DDD concept: Always-Valid Domain model.

Just to recap, the Specification pattern helps to encapsulate a piece of domain knowledge into a single unit, called specification, and then reuse in different scenarios. There are 4 such scenarios:

  • Data retrieval — When you need to query objects from the database that meet some criteria (those criteria are called specification)

  • In-memory validation — When you need to check that an object in the memory meets the criteria

  • Creation of a new object (Martin Fowler and Eric Evans called it construction-to-order in their original paper) — When you need to create a new, arbitrary object that meets the criteria

  • Bulk updates — When you need to modify a bunch of objects that meet the criteria

Specification pattern
Specification pattern

In this article, we will focus on the second use case for the Specification pattern: in-memory validation.

2. Always-Valid Domain Model

Always-Valid Domain Model is a guideline saying that domain classes should guard themselves from ever becoming invalid.

I wrote about it in this article, but in short, a domain model that is always valid alleviates a lot of maintenance burden. Once a domain object is created, you can be sure that it resides in a valid state; you simply can’t instantiate an invalid domain object.

To adhere to this guideline, you need to maintain a clear boundary between the (always-valid) domain model and the (not-always-valid) external world:

Always-Valid Domain Model
Always-Valid Domain Model

So, what is the controversy between these two concepts all about?

3. Specification Pattern vs Always-Valid Domain Model

The controversy comes into play when we look at the second use case for the Specification pattern: in-memory validation (or just validation).

Let’s say that we need to instantiate a student. Let’s also say that all students must have a name and an email address, and that the email address must reside in the .edu domain.

This is how the Student may look when written with the Always-Valid Domain Model guideline in mind:

public class Student
{
    public string Name { get; private set; }
    public string Email { get; private set; }

    public Student(string name, string email)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentException(nameof(name));
        if (string.IsNullOrWhiteSpace(email))
            throw new ArgumentException(nameof(email));

        if (email.Contains("@") == false)
            throw new ArgumentException(nameof(email));
        if (email.EndsWith(".edu") == false)
            throw new ArgumentException(nameof(email));

        Name = name;
        Email = email;
    }
}

Notice that the email validity is a precondition to the student creation. This precondition protects the system from registering students with non-edu email addresses, which are invalid according to our business rules.

Now. How to implement these same validations using the Specification pattern?

Well, we could create the following specification:

public sealed class ValidStudentSpecification : Specification<Student>
{
    public override Expression<Func<Student, bool>> ToExpression()
    {
        return x => string.IsNullOrWhiteSpace(x.Name) == false
            && string.IsNullOrWhiteSpace(x.Email) == false
            && x.Email.Contains("@")
            && x.Email.EndsWith(".edu");
    }
}

public abstract class Specification<T>
{
    public bool IsSatisfiedBy(T entity)
    {
        Func<T, bool> predicate = ToExpression().Compile();
        return predicate(entity);
    }

    public abstract Expression<Func<T, bool>> ToExpression();
}

And then use it like this:

var specification = new ValidStudentSpecification();
var invalidStudent = new Student("Carl", "[email protected]");

bool result = specification.IsSatisfiedBy(invalidStudent);
// result is false

But in order to do so, we would have to bring the student into a potentially invalid state first (which, in turn, requires us to remove all the preconditions from the student constructor).

This is where the contradiction comes into play. In order to validate a new domain object using the Specification pattern, we have to instantiate that object first, which may bring it into an invalid state, thus violating the Always-Valid Domain Model guideline.

So, which of the two approaches to choose?

Adhere to the Always-Valid Domain Model guideline at all times.

An always-valid domain model is one of the most foundational principles in programming. Never give it up.

But does it mean you can’t use the Specification pattern for input validation?

No, it doesn’t. There still are validation use cases where the Specification pattern is applicable.

4. Specification pattern and validation

All validations can be divided into 2 categories:

  • Validations that work with already existing objects,

  • Validations that work with new objects.

The Specification pattern is useful only for validating already existing objects. You shouldn’t use this pattern when validating new objects:

Specification pattern and validation
Specification pattern and validation

Specifications define characteristics of your domain classes. Specifications, by definition, work on top of already existing domain objects. They can’t characterize an object that doesn’t yet exist.

The ValidStudentSpecification naming is a misnomer. If a Student object resides in an invalid state, that object ceases being a student. That’s because domain classes are defined by invariants. Domain classes are what they are because of those invariants. A student that doesn’t comply with all invariants simply isn’t a student anymore.

Alright, but what would be an example of a valid use of the Specification pattern?

A valid use case would be enrolling a student into a course. Let’s say that we can enroll a student in a new course only when that student is active. If a student is inactive, the enrollment attempt must return a validation error.

Here’s what the corresponding specification would look like:

public sealed class ActiveStudentSpecification : Specification<Student>
{
    public override Expression<Func<Student, bool>> ToExpression()
    {
        return x => x.IsActive;
    }
}

And this is how it can be used:

public class Student
{
    public Result CanEnrollIn(Course course)
    {
        bool isActive = new ActiveStudentSpecification().IsSatisfiedBy(this);
        if (isActive == false)
            return Result.Failure("Inactive students can't enroll in new courses");

        return Result.Success();
    }

    public void EnrollIn(Course course)
    {
        Result canEnrollResult = CanEnrollIn(course);
        if (canEnrollResult.IsFailure)
            throw new InvalidOperationException(canEnrollResult.Error);

        /* Enrollment logic */
    }
}

Or you can encapsulate the enrollment applicability into one specification and remove the CanEnrollIn method altogether, like this:

public class Student
{
    public void EnrollIn(Course course)
    {
        bool canEnroll = new QualifiedForNewEnrollmentsSpecification().IsSatisfiedBy(this);
        if (canEnroll == false)
            throw new InvalidOperationException();

        /* Enrollment logic */
    }
}

Once again, this is a good use case for the Specification pattern because we already have an existing student instance. Specifications can derive properties of existing objects only; they can’t work with objects that don’t yet exist.

5. Summary

  • Specification pattern is about reusing bits of domain logic in different scenarios, including validation

  • Always-Valid Domain Model is a guideline saying that you shouldn’t ever allow your domain objects to enter an invalid state

  • There are two types of input validation:

    • Validations of already existing objects

    • Validations of new objects

  • When applied to validating of new objects, the Specification pattern violates the Always-Valid Domain Model guideline

  • Use specifications only for validation of already-existing objects

Subscribe


I don't post everything on my blog. Don't miss smaller tips and updates. Sign up to my mailing list below.

Comments


comments powered by Disqus