Query store (experimental)
|
The query store is experimental: it may change or be removed. Use the JPA store for read models that must persist. |
A @QueryProjection keeps its read models in the framework’s tables in the application’s database, and lets the application query them by chosen properties.
The read model is a plain class, stored as JSON.
Setup
Add spring-ddd-starter-cqrs-jdbc-query.
Two settings apply:
-
spring.ddd.cqrs.projection.query.enableddefaults totrue; withfalsethe store is left out, and a@QueryProjectionthen fails the startup. -
spring.ddd.cqrs.jdbc.initialize-schemaisalways(the default),embeddedornever, and says where the framework creates a missing table. It also controls theREAD_MODEL_EPOCHStable, soneverstops that table being created too, for JPA projections as well.An existing table is never altered, only checked at startup, and a missing one fails the startup where tables are not created.
Declaring
@QueryProjection(name, errorHandler, table) declares the projection.
Each projection has one table of its own, named after the projection unless table names it: letters, digits and _, not starting with a digit, at most 63 characters.
@QueryField marks a property the application queries by.
unique makes its value unique across the read models, and type sets how it is stored and sorted: TEXT (the default), NUMBER, TEMPORAL or BOOLEAN.
class AccountSummary(
@ReadModelKey var accountId: AccountId = AccountId(),
@QueryField(unique = true) var iban: String = "",
@QueryField var owner: String = "",
@QueryField(type = QueryFieldType.NUMBER) var balance: BigDecimal = BigDecimal.ZERO,
)
@QueryProjection(name = "account-summary", errorHandler = DeadLetterKeyErrorHandler::class)
class AccountSummaryProjection {
@InitReadModel
fun init(id: AccountId) = AccountSummary(accountId = id)
// listeners, see Writing a projection
}
Querying
QueryProjectionRepository<T, ID> offers the application’s queries.
Finder names, OrderBy, Sort and Pageable may name @QueryField properties only, and the usual operators are allowed.
A finder returns a single result only for one equality on a unique field.
A finder the store cannot run fails the startup.
interface AccountSummaryRepository : QueryProjectionRepository<AccountSummary, AccountId> {
fun findByIban(iban: String): AccountSummary?
fun findByOwnerOrderByBalanceDesc(owner: String): List<AccountSummary>
}
The repository’s id type is the type of the read model’s own id; see The read model’s own id. To serve the read models over HTTP, see Read models over HTTP.
Ceilings
-
Text sorts as text: a
BigDecimalkept asTEXTdoes not sort numerically, so declare itNUMBER. -
A text field holds up to 255 characters.
-
Times are kept to the microsecond, in UTC; the stored read model keeps full precision.
-
An
InorNotInlist of more than about 2100 elements fails on SQL Server. -
On MySQL, MariaDB and SQL Server, whose default collations ignore case, text equality ignores case.