Composable Predicates: The Specification Pattern
The **Specification Pattern** is used to encapsulate a business rule as a single, reusable boolean predicate. These specifications can be combined using logical operators (AND, OR, NOT) to build complex, dynamic business rules or database queries.
I. The Problem: Method Explosion
Without specifications, repositories often end up with a "Method Explosion":
`findByStatus`, `findByStatusAndType`, `findByStatusAndTypeAndDate`...
The Specification pattern solves this by allowing the client to pass a single, composed predicate to a generic `findAll(Specification spec)` method.
II. Type-Safe Composition
Specifications are essentially objects that implement a "matches" or "isSatisfiedBy" method.
```java
public interface Specification<T> {
boolean isSatisfiedBy(T candidate);
default Specification<T> and(Specification<T> other) {
return candidate -> this.isSatisfiedBy(candidate) && other.isSatisfiedBy(candidate);
}
}
```
III. Concrete Example: Dynamic Search with JPA Criteria
In a Spring Data environment, we use `org.springframework.data.jpa.domain.Specification` to map domain rules directly to SQL.
```java
public class PageSpecs {
public static Specification<WikiPage> isType(String type) {
return (root, query, cb) -> cb.equal(root.get("type"), type);
}
public static Specification<WikiPage> inCluster(String cluster) {
return (root, query, cb) -> cb.equal(root.get("cluster"), cluster);
}
}
// Usage in Service
Specification<WikiPage> search = PageSpecs.isType("article")
.and(PageSpecs.inCluster("java"));
List<WikiPage> results = repository.findAll(search);
```
IV. Technical Integrity and Use Cases
1. **Validation:** Use Specifications to validate domain objects before saving.
`if (!orderSpecs.canShip().isSatisfiedBy(order)) { throw ... }`
2. **Selection:** Filter in-memory collections using the same logic used for database queries.
3. **Refactor for Reusability:** If a rule like "Is Authoritative" appears in search, sidebar logic, and API filters, it **must** be a Specification to ensure consistency across the system.
4. **Performance:** Be careful with the JPA Criteria API. It is powerful but can generate inefficient SQL if predicates are nested too deeply or if joins are not managed correctly.
---
**See Also:**
- [Repository Pattern](RepositoryPattern) — Consuming specifications.
- [Functional Programming Principles](FunctionalProgrammingPrinciples) — The theoretical foundation of predicates.
- [Design Patterns Hub](DesignPatternsHub) — Architectural pattern index.