Specification

Encapsulates boolean-returning business rules into composable objects (AND, OR, NOT).

Context

Complex eligibility rules: "is a premium customer AND lives in the EU AND has made >5 purchases this year".

Problem

Spreading these rules with if statements throughout the code makes them inconsistent.

Solution

Each rule is a Specification<T> with IsSatisfiedBy(T). They are combined using operators.

#Example in C#

public abstract class Spec<T>
{
    public abstract bool IsSatisfiedBy(T candidate);
    public Spec<T> And(Spec<T> other) => new AndSpec<T>(this, other);
    public Spec<T> Or(Spec<T> other)  => new OrSpec<T>(this, other);
}

internal class AndSpec<T> : Spec<T>
{
    private readonly Spec<T> _a, _b;
    public AndSpec(Spec<T> a, Spec<T> b) { _a = a; _b = b; }
    public override bool IsSatisfiedBy(T c) => _a.IsSatisfiedBy(c) && _b.IsSatisfiedBy(c);
}

public class IsPremium : Spec<Customer>
{ public override bool IsSatisfiedBy(Customer c) => c.Tier == "premium"; }

public class LivesInEU : Spec<Customer>
{ public override bool IsSatisfiedBy(Customer c) => EuCountries.Contains(c.Country); }

var eligible = new IsPremium().And(new LivesInEU());
bool ok = eligible.IsSatisfiedBy(customer);
Tradeoffs
Pro Con
Reusable and testable rules More classes per rule
Fits with DDD Difficult to translate to SQL if used as a repository filter

#extra #ddd #rules