Using JSONPath handlers
This page describes how to use JSONPath handlers in your projection definitions.To create useful projections you need to process your events in different ways. Serialized supports a number of built-in functions that can be used to transform your events into the desired projection state. The built-in functions use JsonPath expressions to filter what events should be processed, extract data from the event and select the target field in the projection state where the result should be stored.
JsonPath is a simple and concise way to analyse, transform and select data out of a JSON document. With Serialized JsonPath functions you can process your events without writing the transformation code yourself.
It is advised to keep your projections relatively simple. If you find yourself writing complex logic in your projection definition, you should consider if that logic could be represented as a new event on the aggregate, or perhaps writing a custom function instead.
In a handler function you always have access to event data, the projection state and the event metadata.
Configuring a function
Each of the pre-defined functions have a specific purpose and are used to transform your events into the desired state in a specific way. Each function also has a number of supported attributes that can be used to configure the function.
The following is a list of attributes that the functions can support:
- Event filter - Defines which events should be processed by the function, based on the event data and metadata. This is a JSONPath filter which is empty by default.
- Event selector - Defines which part of the event should be used as input to the function. This is a JSONPath
expression with default value of
$.event
. - Target filter - Looks into the current state of the projection and decides if the function should be executed. This is a JSONPath filter which is empty by default.
- Target selector - The target selector defines where the result of the function should be stored in the projection
state. This is a JSONPath expression with default value of
$.projection
.
The complete collection of supported pre-defined JSONPath functions and their supported attributes can be found in the reference section.
Event metadata
Additionally, there is also a $.metadata
struct containing the fields aggregateId
, timestamp
, createdAt
, updatedAt
and optionally tenantId
and reference
.
Filtering based on event data
A common scenario is to filter events based on the event data. To implement this, you can configure the function to use
an eventFilter
. The event filter is a JSONPath filter expression that is evaluated against the event. If the
expression evaluates to true, the event will be processed by the function. If the expression evaluates to false, the
event will be ignored by the function.
For example, if you have an OrderPlaced
event that contains the customer loyalty status, such as GOLD
or SILVER
,
you can use that data to create a projection definition that only will process orders for gold members.
The following example will define a projection called gold-member-orders
that will contain all placed orders for
gold members. Any order placed by a silver member will be ignored.
var mergeGoldMemberOrders = newHandler("OrderPlaced") .addFunction(merge() .with(eventFilter("[?(@.customer.loyaltyStatus=='GOLD')]")) .build()) .build();var definition = singleProjection("gold-member-orders") .feed("order") .addHandler(mergeGoldMemberOrders) .build();projectionClient.createOrUpdate(definition)
Filtering based on projection state
To apply a condition based on the state of your projection, you need to use targetFilter
. Target filters are used
together with target selectors to decide if, and where the data should be written to the projection.
If the filtered expression does not match anything in the projection, the function will not be executed and the next function in the event handler will be evaluated.
Applying target filters is useful for many use cases. For example, if you want to create a projection that adds information to a certain part of a projection or if the projection is in a certain state.
The following example shows how to create a projection that only adds information to the customer
part of the state if
the customer is a GOLD member.
var addPointsForGoldMembers = newHandler("OrderPlaced") .withIdField("customerId") .addFunction(Functions.merge().build()) .addFunction(Functions.add() .with(eventSelector("points")) .with(new TargetSelector("$.projection[?].loyaltyPoints"))) .with(targetFilter("[?(@.loyaltyStatus=='GOLD')]")) .build()) .build(); var definition = singleProjection("customer-loyalty") .feed("order") .addHandler(addPointsForGoldMembers) .build();
Providing raw data
If you have a static value that you want to use as the data for the projection instead of some value that is provided in
the event, you can use the rawData
argument instead of the eventSelector
.
You can then provide any JSON value which will be used as it is in the projection definition, number, strings, objects and arrays.
In the following example, a projection definition called customer-status
is created. This definition will give newly
registered customers the loyalty status of BRONZE
.
var setBronzeStatusForNewCustomers = newHandler("CustomerRegistered") .addFunction(Functions.set() .with(targetSelector("loyaltyStatus")) .with(rawData("BRONZE")) .build()) .build(); var definition = singleProjection("customer-status") .feed("customer-registrations") .addHandler(setBronzeStatusForNewCustomers) .build(); projectionClient.createOrUpdate(definition)
More on filters
All Filter Operators that are described here are supported.