Documentation
Welcome to docs|Projections|Using ID-based projections

Using ID-based projections

This page describes how to use ID-based projections to query your data.

The simplest and most common way to project an aggregate is to project it by ID. This type of projections are called single projections. With single projections, the ID of the aggregate is used as the key for the projection by default, but it is possible to customize the ID by using the idField property in the projection definition.

Single projections also support filtering using references which can be used for listings as well as indexed fields, which can make projections searchable.

In a single projection, every event for the feed specified in the projection definition will be processed by each handler and the output will be written to the target projection ID, which by default is the aggregate ID of the event being processed.

This page will show how ID-based projections are created and how you can query them from your application.

Creating single ID-based projections

To create a single projection, you need to create a projection definition of type single. Using our SDKs there is typed support for creating such a definition, but you can also create the definition manually using the API.

In the following example, a projection called orders-by-id is created, that will contain all orders in the system. A new projection will be created whenever an OrderPlaced event is emitted. The merge function is used to merge in all data from the OrderPlaced event into the projection.

var definition = singleProjection("orders-by-id")        .feed("order")        .addHandler(newHandler("OrderPlaced")            .addFunction(Functions.merge().build())            .build())        .build()projectionClient.createDefinition(definition);
const definition = {    feedName: 'order',    projectionName: 'orders-by-id',    handlers: [         {            eventType: 'OrderPlaced',            functions: [{function: 'merge'}]        }    ]}await projectionsClient.createOrUpdate(definition);

Querying a single projection

To query a single projection, you can use typed methods in the SDKs or use the API directly. You need to provide the projection name and the ID of the projection you want to query.

In the following example, the orders-by-id projection is queried for an order with ID 45f184b5-f5a5-4c7e-88c0-5ed246675478:

var request = ProjectionQueries.single("orders-by-id")        .withId("45f184b5-f5a5-4c7e-88c0-5ed246675478")        .build(OrderProjection.class);OrderProjection projection = projectionClient.<OrderProjection>query(request).data();
const request = {projectionName: 'orders-by-id', projectionId: '45f184b5-f5a5-4c7e-88c0-5ed246675478'};const projection = (await projectionsClient.getSingleProjection(request)).data

Listing single projections

Since there can be many single projections produced by the same projection definition, it is useful to be able to list all single projections matching a specific name.

In the following example, all projected orders produced by the definition named orders-by-id are listed:

var request = ProjectionQueries.list("orders-by-id").build(OrderProjection.class);List<OrderProjection> orders = projectionClient.<OrderProjection>query(request).projections()    .stream()    .map(ProjectionResponse::data)    .collect(toList());
const request = {projectionName: 'orders-by-id'};const orders = (await projectionsClient.listSingleProjections(request))    .projections    .map(p => p.data)

Paginating projection list results

To handle scenarios where projection list queries returns many projections, it is possible to paginate through the results. This is done by using Skip and Limit in list query.

Below is an example of how to use skip and limit to paginate through the results for orders-by-id projection. The example will skip the first 10 orders and then return at most 20 orders in the response.

var request = ProjectionQueries.list("orders-by-id")    .limit(20)    .skip(10)    .build(OrderProjection.class);    List<OrderProjection> orders = projectionClient.<OrderProjection>query(request).projections()    .stream()    .map(ProjectionResponse::data)    .collect(toList());
const request = {projectionName: 'orders-by-id', limit: 20, skip: 10};const orders = (await projectionsClient.listSingleProjections(request))    .projections    .map(p => p.data)

Deleting a single projection

It is not possible to explicitly delete a single projection. Instead, you should delete the projection by reacting to an event that should delete the matching projection. Add a corresponding handler to your projection definition and use the delete function to delete the projection, if that event occurs for the given projection ID.

In the following example, a projection will be deleted whenever a matching OrderCanceled event is processed:

// Merge in all data from the OrderPlaced eventvar mergeOnOrderPlaced = newHandler("OrderPlaced").addFunction(Functions.merge().build()).build()// Delete the order projection when an OrderCanceled event occursvar deleteOnOrderCanceled = newHandler("OrderCanceled").addFunction(Functions.delete().build()).build()var definition = singleProjection("orders-by-id")        .feed("order")        .addHandler(mergeOnOrderPlaced)        .addHandler(deleteOnOrderCanceled)        .build()projectionClient.createDefinition(definition);
// Merge in all data from the OrderPlaced eventconst mergeOnOrderPlaced = {  eventType: 'OrderPlaced',  functions: [{function: 'merge'}],}// Delete the order projection when an OrderCanceled event occursconst deleteOnOrderCanceled = {  eventType: 'OrderCanceled',  functions: [{function: 'merge'}],}await projectionsClient.createOrUpdate({  feedName: 'orders',  projectionName: 'orders-by-id',  handlers: [mergeOnOrderPlaced, deleteOnOrderCanceled]})