Filtering projections using references
This page describes how projections can be filtered and queried using references.Projections can be uniquely identified by their ID, but they can also contain an optional Reference. A reference can be added as additional metadata to the projection when it is saved to make it possible to find it using other criteria than the projection ID. References are not unique, so in contrast to the projection ID, it is possible to have multiple projections with the same reference. A reference can be seen as a non-unique secondary index.
Common scenarios for using references are:
- Tagging projections with a field, such as
LARGE
,MEDIUM
orSMALL
orders - Adding a date or timestamp reference to enable finding all projections within a given time range
- Using find-first semantics to find the first projection matching a given string
References work like secondary index on projection data, so any data you want to use as a reference must be present in
the projection. For example, if you want to tag orders with a LARGE
, MEDIUM
or SMALL
tag, you must add a size
field to the order projection, containing those tags.
Configuring the projection reference
To mark a field as a reference, the setRef
function must be used in one of the handlers in the projection definition.
For example, to be able to filter by shipment code, the reference can be applied to the shipmentCode
field.
// Merge the event data into the projection (including shipmentCode)var mergeFunction = Functions.merge().build()// Use the merged shipmentCode as a referencevar referenceFunction = Functions.setref().with(targetSelector("shipmentCode")).build()projectionClient.createDefinition(singleProjection("orders-by-shipment-code") .feed("order") .addHandler( newHandler("OrderShipped") .addFunction(mergeFunction) .addFunction(referenceFunction) .build()) .build());
Querying the projection using the reference
You can now query your shipment projections with a reference that will result in a match for any projection that
matches shipmentCode
field. Since references are not unique, you will get a list of all projections that match the
reference in the query.
var response = projectionClient.query(list("orders-by-shipment-code") .withReference("ABC123") .build(OrderProjection.class)); var matchingOrders = response.projections() .stream() .map(ProjectionResponse::data) .toList()
Listing projections with references also support pagination, so you can navigate through all matching projections in a controlled manner.
Implementing from/to filtering using references
If your projections contain a timestamp or date field, or something else that has a well-defined order, you can use the
reference to implement from/to filtering. For example to find all orders that were shipped after a given date, you can
provide the date field as a reference and add a from
filter to the query.
The following code shows an example where orders are projected and that the timestamp field shippedAt
is being used
as a reference (milliseconds since epoch). The example will list all orders that were shipped since yesterday.
var yesterday = Instant.now().minus(1, ChronoUnit.DAYS).toEpochMilli()var response = projectionClient.query(list("orders-by-shipment-date") .withFrom(yesterday) .build(OrderProjection.class)); var matchingOrders = response.projections() .stream() .map(ProjectionResponse::data) .toList()