How DDDplus Implements the Specification Pattern with AndSpecification and OrSpecification

DDDplus implements the Specification pattern through a composable hierarchy where AbstractSpecification provides and() and or() methods that return AndSpecification and OrSpecification instances, enabling logical conjunction and disjunction of business rules.

The funkygao/cp-ddd-framework (DDDplus) provides a clean, extensible implementation of the Specification pattern that allows domain developers to encapsulate business rules as discrete, reusable objects. By leveraging AndSpecification for logical AND operations and OrSpecification for logical OR operations, DDDplus enables complex validation logic to be built through method chaining while maintaining clean separation of concerns.

Core Components of the DDDplus Specification Pattern

ISpecification Interface

The foundation of the pattern is the ISpecification<T> interface located in dddplus-spec/src/main/java/io/github/dddplus/model/spcification/ISpecification.java. This contract defines two overloaded methods for evaluating whether a candidate object satisfies the specification:

public interface ISpecification<T> {
    boolean isSatisfiedBy(T candidate);
    boolean isSatisfiedBy(T candidate, Notification notification);
}

The second method accepts a Notification object that aggregates validation errors, allowing specifications to collect multiple failure reasons without throwing exceptions immediately.

AbstractSpecification Base Class

The AbstractSpecification<T> class in dddplus-spec/src/main/java/io/github/dddplus/model/spcification/AbstractSpecification.java provides the compositional infrastructure. It implements the basic isSatisfiedBy(T candidate) by delegating to the notification-aware version, and crucially, it defines the and() and or() factory methods:

public abstract class AbstractSpecification<T> implements ISpecification<T> {
    @Override
    public final boolean isSatisfiedBy(T candidate) {
        return isSatisfiedBy(candidate, Notification.build());
    }

    public AbstractSpecification<T> and(final ISpecification<T> specification) {
        return new AndSpecification<T>(this, specification);
    }

    public AbstractSpecification<T> or(final ISpecification<T> specification) {
        return new OrSpecification<T>(this, specification);
    }
}

These methods enable fluent chaining, allowing developers to compose complex logical expressions from simple specifications.

How AndSpecification Implements Logical AND

The AndSpecification<T> class in dddplus-spec/src/main/java/io/github/dddplus/model/spcification/AndSpecification.java implements logical conjunction. It stores references to two inner specifications—left and right—and evaluates both when checking satisfaction:

public class AndSpecification<T> extends AbstractSpecification<T> {
    private ISpecification<T> left;
    private ISpecification<T> right;

    public AndSpecification(final ISpecification<T> left,
                            final ISpecification<T> right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public boolean isSatisfiedBy(T candidate, Notification notification) {
        return left.isSatisfiedBy(candidate, notification) &&
               right.isSatisfiedBy(candidate, notification);
    }
}

The implementation uses short-circuit evaluation naturally through the && operator. If the left specification fails, the right specification is still evaluated (to collect all errors in the Notification), but the final result will be false.

How OrSpecification Implements Logical OR

The OrSpecification<T> class in dddplus-spec/src/main/java/io/github/dddplus/model/spcification/OrSpecification.java provides logical disjunction. Similar to AndSpecification, it wraps two specifications but uses the || operator to determine satisfaction:

public class OrSpecification<T> extends AbstractSpecification<T> {
    private ISpecification<T> left;
    private ISpecification<T> right;

    public OrSpecification(final ISpecification<T> left,
                           final ISpecification<T> right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public boolean isSatisfiedBy(T candidate, Notification notification) {
        return left.isSatisfiedBy(candidate, notification) ||
               right.isSatisfiedBy(candidate, notification);
    }
}

This implementation satisfies the specification if either the left or right rule passes. Note that due to short-circuiting with ||, if the left specification succeeds, the right specification is not evaluated.

Notification and Error Aggregation

Both AndSpecification and OrSpecification rely on the Notification class located in dddplus-spec/src/main/java/io/github/dddplus/model/spcification/Notification.java to collect validation errors. Rather than throwing exceptions immediately, specifications add error messages to the Notification instance, allowing composite specifications to accumulate multiple failure reasons across and() and or() chains.

Practical Usage Examples

Creating a Concrete Specification

Domain developers extend AbstractSpecification to implement specific business rules:

class IntegerGreaterThanSpec extends AbstractSpecification<Integer> {
    private final int threshold;
    
    IntegerGreaterThanSpec(int threshold) { 
        this.threshold = threshold; 
    }

    @Override
    public boolean isSatisfiedBy(Integer candidate, Notification notification) {
        if (candidate <= threshold) {
            notification.addError(
                String.format("candidate:%d is not more than %d", candidate, threshold));
            return false;
        }
        return true;
    }
}

Chaining with AndSpecification

Combine multiple specifications using the and() method to create an AndSpecification:

ISpecification<Integer> spec = new IntegerGreaterThanSpec(1)
        .and(new IntegerGreaterThanSpec(2))
        .and(new IntegerGreaterThanSpec(3));

boolean ok = spec.isSatisfiedBy(4);                // true
Notification note = Notification.build();
boolean fails = spec.isSatisfiedBy(2, note);       // false
System.out.println(note.first());                  // "candidate:2 is not more than 3"

Chaining with OrSpecification

Use the or() method to create an OrSpecification for alternative validation paths:

ISpecification<Integer> spec = new IntegerGreaterThanSpec(1)
        .or(new IntegerGreaterThanSpec(2))
        .or(new IntegerGreaterThanSpec(3));

assert spec.isSatisfiedBy(2);   // true – the second spec passes
assert !spec.isSatisfiedBy(1); // false – none of the specs pass (wait, actually 1 > 1 is false, 1 > 2 is false, 1 > 3 is false) - correct

Integration in Domain Services

Specifications integrate cleanly with domain services for validation:

public class OrderService {
    private final ISpecification<Order> orderValidSpec;

    public OrderService(ISpecification<Order> orderValidSpec) {
        this.orderValidSpec = orderValidSpec;
    }

    public void place(Order order) {
        Notification note = Notification.build();
        if (!orderValidSpec.isSatisfiedBy(order, note)) {
            throw new IllegalArgumentException("Order validation failed: " + note.first());
        }
        // proceed with business logic…
    }
}

Summary

  • DDDplus implements the Specification pattern through the ISpecification<T> interface and AbstractSpecification<T> base class in the dddplus-spec module.
  • AndSpecification combines two specifications with logical AND, requiring both left and right specifications to pass.
  • OrSpecification combines two specifications with logical OR, requiring either left or right to pass.
  • Both composite specifications use the Notification class to aggregate validation errors without throwing immediate exceptions.
  • Method chaining via and() and or() enables fluent construction of complex validation rules from simple, reusable specifications.

Frequently Asked Questions

What is the Specification pattern in DDDplus?

The Specification pattern in DDDplus is a design pattern used to encapsulate business rules as discrete, reusable objects. Implemented in the dddplus-spec module, it allows domain developers to define validation logic that can be combined using AndSpecification and OrSpecification to form complex rules without duplicating code or creating deep inheritance hierarchies.

How does AndSpecification differ from OrSpecification in DDDplus?

AndSpecification requires both the left and right specifications to return true for the composite to pass, implementing logical conjunction. In contrast, OrSpecification passes if either the left or right specification returns true, implementing logical disjunction. Both classes extend AbstractSpecification and are instantiated through the and() and or() factory methods respectively.

Can I chain multiple AndSpecification and OrSpecification calls?

Yes, DDDplus supports fluent chaining of specifications. Since both and() and or() return AbstractSpecification<T>, you can chain multiple calls to build complex logical expressions. For example, spec1.and(spec2).or(spec3).and(spec4) creates a composite specification that evaluates according to the rules of operator precedence and short-circuit evaluation.

How does DDDplus handle errors when specifications fail?

Rather than throwing exceptions immediately, DDDplus uses a Notification object passed to isSatisfiedBy(candidate, notification). When a specification fails, it adds error messages to this notification. For composite specifications like AndSpecification and OrSpecification, the same notification instance is passed to child specifications, allowing aggregation of all validation errors across the entire specification chain.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →