Specifications

A specification encapsulates a business criteria and is able to tell if a candidate object matches this criteria.

Characteristics

Parameterized

A specification can define attributes for values that commonly vary. This allows to reuse a specification in various situations.

Composite

Multiple specifications can be combined together with «and», «or» and «not» operators to form more complex specifications. This characteristic allows for great flexibility in the specification definition, without requiring many specialized classes.

Declaration

The business framework provides a Specification interface that all specification classes must implement. This interface directly provides an implementation for:

  • The any() static method to create a specification that is satisfied by any candidate object,
  • The none() static method to create a specification that is satisfied by no candidate object,
  • The and(), or() and negate() methods for composing it with other specifications,
  • The asPredicate() method to convert the specification to a Java predicate.

To create a specification, simply implement the Specification interface and implement the isSatisfiedBy() method:

public class SomeSpecification implements Specification<SomeClass> {
    private final int someValue;
    
    public SomeSpecification(int someValue) {
        this.someValue = someValue;
    }    
    
    public boolean isSatisfiedBy(SomeClass candidate) {
        // return true if the candidate satisfies the specification
        // or false otherwise
    }
}

Usage

In the business framework, specifications are simple objects that are created through their constructors. You can then use the isSatisfiedBy() method directly:

public class SomeClass {
    public void someMethod(SomeClass candidate) {
        SomeSpecification someSpecification = new SomeSpecification(45);
        if (someSpecification.isSatisfiedBy(candidate)) {
            // do something
        }
    }
}

As unmanaged objects, specifications do not benefit from dependency injection nor interception. Dependencies, if any, must be passed explicitly.

For querying

When you need to select a subset of aggregates based on some criteria, a specification can express this criteria on the domain model. It can then be used directly by a repository to efficiently retrieve a Stream of aggregates matching this specification.

For validation

When you need to check that only suitable objects are used for a certain purpose, a specification can express this suitability criteria.

For construction-to-order

When you need to describe what an object might do, without explaining the details of how the object does it, but in such a way that a candidate might be built to fulfill the requirement, a specification can express this requirement.

Built-in specifications

The business framework provides several simple specifications that can be composed to express a more complex one. One of the most useful is the AttributeSpecification which allows to apply another specification to an object attribute.

Consider the following example:

public class SomeClass {
    public void someMethod() {
        Specification<Team> redTeamSpecification = new AttributeSpecification<>(
            "name", 
            new EqualSpecification<>("RED")
        );
    }
}

This creates a specification that can be used to test if a candidate Team object has its attribute name equal to RED.

Specification builder

The business framework provides an injectable SpecificationBuilder that exposes a Domain-Specific Language (DSL) for creating complex specifications by composition:

public class SomeClass {
    @Inject
    private SpecificationBuilder specificationBuilder;
    
    public void someMethod(Team candidate) {
        Specification<Team> spec = specificationBuilder.of(Team.class)
                .property("leader.name").not().equalTo("Alice")
                .or()
                .property("leader.name").not().equalTo("Roger")
                .or()
                .property("leader.age").lessThan(15).and()
                .property("leader.address.number").not().equalTo(5).and()
                .property("leader.address.city").equalTo("SomeCity")
                .build();
    }
}

The specification builder rely on expressing the boolean predicates in the disjunctive normal form (DNF). A specification in DNF is a disjunction (OR) of conjunctive clauses (AND). In other words it requires that the predicates are expressed as an OR clause of AND clauses.

The specification builder offers various additional features. Please check the Javadoc for more details.

   

On this page


Edit