Query Handlers
The handling of a query comes down to an annotated handler returning the query’s response. The goal of this chapter is to describe what such an @QueryHandler
annotated method looks like, as well as describing the call order and response type options. For configuration of query handlers and the QueryBus
, it is recommended to read the Configuration section.
Writing a query handler
In Axon, an object may declare a number of query handler methods, by annotating them with the @QueryHandler
annotation. The object in question is what you would refer to as the Query Handler, or Query Handling Component. For a query handler method, the first declared parameter defines which query message object it will receive.
Taking the 'Gift Card' domain which contains a CardSummary
Query Model, we can assume there is a query message to fetch a single CardSummary
instance. Let us define the format of the query message as follows:
public class FetchCardSummaryQuery {
private final String cardSummaryId;
public FetchCardSummaryQuery(String cardSummaryId) {
this.cardSummaryId = cardSummaryId;
}
// omitted getters, equals/hashCode, toString functions
}
As shown, we have a regular POJO that will fetch a CardSummary
based on the cardSummaryId
field. This FetchCardSummaryQuery
will be dispatched to a handler that defines the given message as its first declared parameter. The handler will likely be contained in an object which is in charge of or has access to the CardSummary
model in question:
import org.axonframework.queryhandling.QueryHandler;
public class CardSummaryProjection {
private Map<String, CardSummary> cardSummaryStorage;
@QueryHandler // 1.
public CardSummary handle(FetchCardSummaryQuery query) { // 2.
return cardSummaryStorage.get(query.getCardSummaryId());
}
// omitted CardSummary event handlers which update the model
}
From the above sample, we want to highlight two specifics when it comes to writing a query handler:
-
The
@QueryHandler
annotation which marks a function as a query handler method. -
The method in question is defined by the return type
CardSummary
, which is called the query response type, and theFetchCardSummaryQuery
which is the query payload.
Storing a query model
For the purpose of the example we have chosen to use a regular |
Query handler call order
In all circumstances, at most one query handler method is invoked per query handling instance. Axon will search for the most specific method to invoke, using following rules:
-
On the actual instance level of the class hierarchy (as returned by
this.getClass()
), all annotated methods are evaluated -
If one or more methods are found of which all parameters can be resolved to a value, the method with the most specific type is chosen and invoked
-
If no methods are found on this level of the class hierarchy, the super type is evaluated the same way
-
When the top level of the hierarchy is reached, and no suitable query handler is found, this query handling instance is ignored.
Note that similar to command handling, and unlike event handling, query handling does not take the class hierarchy of the query message into account.
// assume QueryB extends QueryA
// and QueryC extends QueryB
// and a single instance of SubHandler is registered
public class QueryHandler {
@QueryHandler
public MyResult handle(QueryA query) {
// Return result
}
@QueryHandler
public MyResult handle(QueryB query) {
// Return result
}
@QueryHandler
public MyResult handle(QueryC query) {
// Return result
}
}
public class SubQueryHandler extends QueryHandler {
@QueryHandler
public MyResult handleEx(QueryB query) {
// Return result
}
}
In the example above, the handler method of SubQueryHandler
will be invoked for queries for QueryB
and result MyResult
the handler methods of QueryHandler
are invoked for queries for QueryA
and QueryC
and result MyResult
.
Query handler return values
Axon allows a multitude of return types for a query handler method, as defined earlier on this page. You should think of single objects and collections of objects, taking into account wildcards or generics too. Below we share a list of all the options which are supported and tested in the framework.
For clarity, we make a deviation between single instance and multiple instances of a response type. This follows the requirement to specify the ResponseType
when dispatching a query, which expects the user to state if either a single result or multiple results are desired. Axon will use this ResponseType
object to match a query with a query handler method, alongside the query payload and query name.
Supported single instance return values
To query for a single object, the ReponseTypes#instanceOf(Class)
method should be used to create the required ResponseType
object. This "instance-of-Class`" `ResponseType
object in turn supports the following query handler return values:
-
An exact match of
Class
-
A subtype of
Class
-
A generic bound to
Class
-
A
Future
ofClass
-
A primitive of
Class
-
An
Optional
ofClass
Primitive Return Types
Among the usual Objects, it is also possible for queries to return primitive data types:
Note that the querying party will retrieve a boxed result instead of the primitive type. |
Supported multiple instances return values
To query for a multiple objects, the ReponseTypes#multipleInstancesOf(Class)
method should be used to create the required ResponseType
object. This "multiple-instances-of-Class`" `ResponseType
object in turn supports the following query handler return values:
-
An array containing:
-
Class
-
A subtype of
Class
-
A generic bound to
Class
-
An
Iterable
or a custom implementation ofIterable
containing: -
Class
-
A subtype
Class
-
A generic bound to
Class
-
A wildcard bound to
Class
-
A
Stream
ofClass
-
A
Future
of anIterable
ofClass