Specification Design Pattern

The main idea of FluentSpecification is to prepare complex Specifications solution for all kinds of business cases - not only for validation but also for queries and repositories.

Business Model

Eric Evans in his book Domain-Driven Design describes Specifications, and their impact on Business Model:

Business rules often do not fit the responsibility of any of the obvious ENTITIES or VALUE OBJECTS,
and their variety and combinations can overwhelm the basic meaning of the domain object.
But moving the rules out of the domain layer is even worse,
since the domain code no longer expresses the model.

Logic programming provides the concept of separate, combinable, rule objects called "predicates",
but full implementation of this concept with objects is cumbersome.
It is also so general that it doesn't communicate intent as much as more specialized designs.

Usage

Eric Evans recommends, to use Specifications:

  • To validate an object to see if it fulfills some need or is ready for some purpose
  • To select an object from a collection (as in the case of querying for overdue invoices)
  • To specify the creation of a new object to fit some need

Validation

graph BT InvoiceSpec["Invoice Specification

isSatisfiedBy(Invoice) : boolean"] DelinquentSpec["Delinquent Invoice Specification

current date"]-->|extends|InvoiceSpec BigSpec["Big Invoice Specification

threshold amount"]-->|extends|InvoiceSpec InvoiceSpec-->|uses|Invoice["Invoice

amount
due date
"] Customer["Customer"]-->|* has many|Invoice
class DelinquentInvoiceSpecification extends
    InvoiceSpecification {
    
    private Date currentDate;
    
    // An instance is used and discarded on a single date
    public DelinquentInvoiceSpecification(Date currentDate) {
        this.currentDate = currentDate;
    }
    
    public boolean isSatisfiedBy(Invoice candidate) {
        int gracePeriod =
        candidate.customer().getPaymentGracePeriod();
        Date firmDeadline =
            DateUtility.addDaysToDate(candidate.dueDate(),
                gracePeriod);
        return currentDate.after(firmDeadline);
    }
}
public boolean accountIsDelinquent(Customer customer) {
    Date today = new Date();
    Specification delinquentSpec =
        new DelinquentInvoiceSpecification(today);
        
    Iterator it = customer.getInvoices().iterator();
    while (it.hasNext()) {
        Invoice candidate = (Invoice) it.next();
        if (delinquentSpec.isSatisfiedBy(candidate)) return true;
    }
    
    return false;
}

Querying

public Set selectSatisfying(InvoiceSpecification spec) {
    Set results = new HashSet();
    
    Iterator it = invoices.iterator();
    while (it.hasNext()) {
        Invoice candidate = (Invoice) it.next();
        if (spec.isSatisfiedBy(candidate)) results.add(candidate);
    }
    
    return results;
}
Set delinquentInvoices = invoiceRepository.selectSatisfying(
    new DelinquentInvoiceSpecification(currentDate));

Extending Specifications

In next chapters Eric Evans proposed: extending Specifications in a Declarative Style:

graph BT CompositeSpec["Composite Specification

isSatisfiedBy(Object) : boolean
and(Specification) : Specification
or(Specification) : Specification
not() : Specification
"] AndSpec["AND Specification"]-->|extends|CompositeSpec OrSpec["OR Specification"]-->|extends|CompositeSpec NotSpec["NOT Specification"]-->|extends|CompositeSpec CompositeSpec-->|uses|AndSpec CompositeSpec-->|uses|OrSpec CompositeSpec-->|uses|NotSpec CompositeSpec-->|implements|Specification["Specification

isSatisfiedBy(Object) : boolean"]

Generic

Wikipedia describes next step - generic Specifications with strongly typed candidate.

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T candidate);
    ISpecification<T> And(ISpecification<T> other);
    ISpecification<T> AndNot(ISpecification<T> other);
    ISpecification<T> Or(ISpecification<T> other);
    ISpecification<T> OrNot(ISpecification<T> other);
    ISpecification<T> Not();
}

FluentSpecification

FluentSpecification based on Specification pattern described in book Domain-Driven Design of Eric Evans.
Specifications are generic for strongly typed candidates.
Ofcourse, described above patterns are not implemented one-to-one. About FluentSpecification and how to use it you can read in next chapters.

Validation

In addition, to the usual candidate verification, FluentSpecification prepared special Validation scenarios with result object, described in next chapters.
Special result contains error messages, information about executed Specifications etc.

Querying

To handle querying functionalities, FluentSpecification use Linq expressions - expressions building and usage is described in next chapters of this documentation.

GitHub