-
Notifications
You must be signed in to change notification settings - Fork 0
SpecificationAdvice
Here are some snippets of advice relating to the use of the specification pattern.
Specification expressions are more reusable than functions; so they are almost always the superior choice. Any time you find yourself writing a specification function, consider "Could this be refactored to become an expression?".
Specifications which only test a single property against a constant value are rarely worthwhile coding. For example in an application which operates solely in the UK, a specification CanOrderBeer
which performs the test x => x.Age >= 18
would be of little value.
On the other hand, imagine if this application operated internationally, where the legal age to buy beer differs per jurisdiction. In that case a specification like the following example would be worthwhile.
using CSF.Specifications;
using System.Linq.Expressions;
public class CanOrderBeer : ISpecificationExpression<Person>
{
readonly IGetsMinimumAgeToPurchaseAlcoholForJurisdiction legalAgeProvider;
readonly string jurisdictionCode;
public Expression<Func<Person,bool>> GetExpression()
{
var minAge = legalAgeProvider.GetMinimumAgeToPurchaseAlcohol(jurisdictionCode);
return person => person.Age >= minAge;
}
public CanOrderBeer(IGetsMinimumAgeToPurchaseAlcoholForCulture legalAgeProvider, string jurisdictionCode)
{
this.legalAgeProvider = legalAgeProvider ?? throw new ArgumentNullException(nameof(legalAgeProvider));
this.jurisdictionCode = jurisdictionCode ?? throw new ArgumentNullException(nameof(jurisdictionCode));
}
}
Useful specifications encapsulate some business/domain logic, they should not just test values as if they were flags.
It is possible to use this library to create dyanmic/ad-hoc specification objects. Usually, though, it is best to use specification classes. This way the logic is encapsulated and reusable as a single self-contained concept.
It is perfectly reasonable to create a specification class which does no more than compose other specifications. This is particularly useful where the composition of specifications is in fact a reusable piece of logic in its own right. This is also a case where a dynamic specification might be useful; just for the purpose of composition. Take a look at the example below.
using CSF.Specifications;
using System.Linq.Expressions;
public class InvoicesToSendForDebtCollection : ISpecificationExpression<Invoice>
{
public Expression<Func<Invoice,bool>> GetExpression()
{
var invoicesThatArentWrittenOff = Spec.Expr<Invoice>(x => !x.IsWrittenOff);
var overdueInvoices = new InvoicesThatAreOverdue();
var alreadyInDebtCollection = new InvoicesThatAreWithDebtCollection();
return overdueInvoices
.And(invoicesThatArentWrittenOff)
.And(alreadyInDebtCollection.Not())
.GetExpression();
}
}