Specification Pattern vs Always-Valid Domain Model

1. Specification pattern

Specification pattern

2. Always-Valid Domain Model

Always-Valid Domain Model

3. Specification Pattern vs Always-Valid Domain Model

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

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

4. Specification pattern and validation

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

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