Specification Pattern vs Always-Valid Domain Model

1. Specification pattern

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

  • 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

2. Always-Valid Domain Model

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

Always-Valid Domain Model

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).

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;
}
}
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();
}
var specification = new ValidStudentSpecification();
var invalidStudent = new Student("Carl", "carl@gmail.com");
bool result = specification.IsSatisfiedBy(invalidStudent);
// result is false

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.
Specification pattern and validation
public sealed class ActiveStudentSpecification : Specification<Student>
{
public override Expression<Func<Student, bool>> ToExpression()
{
return x => x.IsActive;
}
}
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 */
}
}
public class Student
{
public void EnrollIn(Course course)
{
bool canEnroll = new QualifiedForNewEnrollmentsSpecification().IsSatisfiedBy(this);
if (canEnroll == false)
throw new InvalidOperationException();
/* Enrollment logic */
}
}

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

6. Related

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store