Documentation
Welcome to docs|Projections|Filtering projections using references

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 or SMALL 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());
// Merge the event data into the projection (including shipmentCode)var mergeFunction = { function: 'merge' }// Use the merged shipmentCode as a referencevar referenceFunction = { function: 'setref', targetSelector: 'shipmentCode' }const definition = {    projectionName: 'orders-by-shipment-code',    feed: 'order',    handlers: [        {            eventType: 'OrderShipped',            functions: [mergeFunction, referenceFunction]        }    ]}await projectionsClient.createDefinition(definition);

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()
const request = {      projectionName: 'orders-by-shipment-code',      reference: 'ABC123'    }const response = await projectionsClient.listSingleProjections(request);const matchingOrders = response.projections.map(m => m.data)

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()
const yesterday = () => {    const timestamp = new Date();    timestamp.setDate(timestamp.getDate() - 1);    return timestamp.getTime();}const request = {      projectionName: 'orders-by-shipment-code',      from: yesterday()    }const response = await projectionsClient.listSingleProjections(request);const matchingOrders = response.projections.map(m => m.data)