Specifications¶
EventFlow ships with an implementation of the specification pattern which could be used to e.g. make complex business rules easier to read and test.
To use the specification implementation shipped with EventFlow, simply create a
class that inherits from Specification<T>
.
public class BelowFiveSpecification : Specification<int>
{
protected override IEnumerable<string> IsNotSatisfiedBecause(int i)
{
if (5 <= i)
{
yield return string.Format("{0} is not below five", i);
}
}
}
Note that instead of simply returning a bool
to indicate whether or not the
specification is satisfied, this implementation requires a reason (or reasons)
why the specification is not satisfied.
The ISpecification<T>
interface has two methods defined, the traditional
IsSatisfiedBy
as well as WhyIsNotSatisfiedBy
, which returns an
empty enumerable if the specification was indeed satisfied.
public interface ISpecification<in T>
{
bool IsSatisfiedBy(T obj);
IEnumerable<string> WhyIsNotSatisfiedBy(T obj);
}
Specifications really become powerful when they are combined. EventFlow also
ships with a series of extension methods for the ISpecification<T>
interface
that allows easy combination of implemented specifications.
// Throws a `DomainError` exception if obj doesn't satisfy the specification
spec.ThrowDomainErrorIfNotStatisfied(obj);
// Builds a new specification that requires all input specifications to be
// satified
var allSpec = specEnumerable.All();
// Builds a new specification that requires a predefined amount of the
// input specifications to be satisfied
var atLeastSpec = specEnumerable.AtLeast(4);
// Builds a new specification that requires the two input specifications
// to be satisfied
var andSpec = spec1.And(spec2);
// Builds a new specification that requires one of the two input
// specifications to be satisfied
var orSpec = spec1.Or(spec2);
// Builds a new specification that requires the input specification
// not to be satisfied
var notSpec = spec.Not();
If you need a simple expression to combine with other more complex specifications
you can use the bundled ExpressionSpecification<T>
, which is a specification
wrapper for an expression.
var spec = new ExpressionSpecification<int>(i => 1 < i && i < 3);
// 'str' will contain the value "i => ((1 < i) && (i < 3))"
var str = spec.ToString();
If the specification isn’t satisfied, a string representation of the expression is returned.