The Specification pattern puts one business rule in its own object. That object has a method like isSatisfiedBy(candidate). Eric Evans and Martin Fowler described this pattern.
You can combine specifications with and, or and not. For example, isActive().and(createdAfter(date)).and(inRegion(r)) replaces a separate repository method for every combination. Each rule also gets a name in the business language. You can unit test it on in-memory objects.
To use it for queries, the repository accepts a specification, as in users.findMatching(spec). The repository then turns it into a database query. You need one translator per storage technology. For example, a translator turns each specification into a where clause for the query builder.
Limits:
- The translation layer adds complexity.
- A specification that works in memory may not translate into efficient SQL.
- For read-heavy queries built for one screen, a separate query service or read model is often simpler. It skips the domain repository.
Use specifications when the criteria are real business rules that you reuse in several places.
- A specification is a business rule as an object with an isSatisfiedBy method.
- You can combine specifications with and, or and not.
- It gives named, testable business rules instead of a growing list of finder methods.
- It needs translation into database queries, which adds complexity.
- Queries for one screen may fit better in a separate query service.