Query API Client
Scope
wow-apiclient.query provides CoApi transport interfaces for remotely calling Snapshot HTTP query contracts. See the API Client extension for dependency installation, @EnableCoApi, service discovery, command clients, and general error types.
These clients are snapshot-only. They do not read the runtime Query Model Schema, validate fields on the client, perform authorization, or replace the server-side QueryGateway and HTTP guards. The running server's OpenAPI remains the source of truth for routes and wire contracts.
Interface Matrix
| Interface | Capability or result | Path relative to @HttpExchange |
|---|---|---|
SnapshotSingleQueryApi | typed, dynamic, and state-only single contracts | snapshot/single, snapshot/single/state |
SnapshotListQueryApi | typed, dynamic, and state-only list contracts | snapshot/list, snapshot/list/state |
SnapshotPagedQueryApi | typed, dynamic, and state-only paged contracts | snapshot/paged, snapshot/paged/state |
SnapshotCountQueryApi | exact count from a FilterExpression | snapshot/count |
SnapshotAggregationQueryApi | dynamic rows from an AggregationQuery | snapshot/aggregation |
ReactiveSnapshotQueryApi | reactive composition of single, list, paged, and count | excludes aggregation |
SynchronousSnapshotQueryApi | synchronous composition of single, list, paged, and count | excludes aggregation |
ReactiveSnapshotAggregationQueryApi | Flux<Map<String, Any?>> | separate aggregation client |
SynchronousSnapshotAggregationQueryApi | List<Map<String, Any?>> | separate aggregation client |
Methods on the first five base interfaces declare the listed paths directly with @PostExchange. Their Reactive and Synchronous interfaces reuse those methods through inheritance, and the regular composite interfaces inherit the corresponding specialized interfaces.
The specialized Reactive and Synchronous interfaces are already inherited by the two regular composite interfaces. Usually choose a composite interface directly; inherit one specialized interface only when a client needs that single capability. There is no need to expose every derived interface as a separate public client.
Declaring Typed Clients
Follow the project's existing CoApi declaration pattern and bind each interface to the aggregate route base:
@CoApi(baseUrl = "http://order-service:8080")
@HttpExchange("cart")
interface CartQueryClient : ReactiveSnapshotQueryApi<CartState>
@CoApi(baseUrl = "http://order-service:8080")
@HttpExchange("cart")
interface CartAggregationClient : ReactiveSnapshotAggregationQueryApiRegister both interfaces that CoApi must materialize in @EnableCoApi(clients = [...]). When CoApi or application conventions require concrete generic metadata, redeclare methods with concrete return types and @RequestBody, as the repository example clients do, but do not repeat the path on every method.
Single, List, Paged, and Count
| Operation | Reactive result | Synchronous result |
|---|---|---|
| single typed / state-only / dynamic | Mono<MaterializedSnapshot<S>> / Mono<S> / Mono<Map<String, Any>> | corresponding nullable values |
| list typed / state-only / dynamic | corresponding Flux | corresponding List |
| paged typed / state-only / dynamic | Mono<PagedList<...>> | PagedList<...> |
| count | Mono<Long> | Long |
ISingleQuery, IListQuery, and IPagedQuery execute through the query, queryState, and dynamicQuery extensions. FilterExpression.count executes a count. getById and getStateById are conveniences that build a single query for an aggregateId.
Complete Snapshot, State-only, and Dynamic Results
- A typed complete snapshot returns
MaterializedSnapshot<S>, retaining bothstateand snapshot system metadata. - A state-only result returns
S. It changes only the response shape; request filters still usestate.*paths. - A dynamic result returns
Map<String, Any>for projections that change the result shape, withoutS's compile-time field type. - Aggregation always returns dynamic
Map<String, Any?>rows; it has no typed or state-only variant.
Separate Aggregation Client
ReactiveSnapshotQueryApi and SynchronousSnapshotQueryApi deliberately exclude aggregation. Declare ReactiveSnapshotAggregationQueryApi or SynchronousSnapshotAggregationQueryApi separately, then post an AggregationQuery to snapshot/aggregation:
val rows: Flux<Map<String, Any?>> = aggregation {
terms("state.status", "status")
count("count")
}.query(cartAggregationClient)Aggregation fields, Element paths, backend capabilities, and cost protection remain server responsibilities; see Snapshot Aggregation.
Reactive and Synchronous
Reactive interfaces use Mono and Flux for non-blocking call chains. Synchronous interfaces return values, List, or PagedList directly and block the calling thread. Do not call a synchronous client from a Reactor event loop or Wow core reactive processing path.
Both variants submit the same query DTOs to the same HTTP paths. Only their invocation and return models differ; server query semantics do not.
404 and Empty-result Semantics
HTTP single returns 404 when no item matches. The provided ISingleQuery.query, queryState, and dynamicQuery helpers, plus getById and getStateById, turn that 404 into an empty reactive Mono or synchronous null. Calling the inherited single, singleState, or dynamicSingle method directly is a raw CoApi transport call and does not pass through those helpers' 404 conversion.
A normal no-match list returns an empty Flux/List; paged returns a PagedList with total = 0 and list = []; count returns 0. None is a single-query 404. Validation, authorization, rate-limit, timeout, and backend errors continue to propagate.
Event Stream Client Is Not Currently Supported
wow-apiclient.query currently provides Snapshot interfaces only. It has no EventStream data-query or aggregation-query client. Published server-side EventStream HTTP routes do not imply a built-in client; if an application needs one, declare it against the actual OpenAPI and preserve the capability boundaries documented in Event Stream Queries.