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