Projecting domain events to useful read models in Java

Learn how to create a basic projection of your events using our Java client library.

When getting started with Event Sourcing you quickly realize that events are a convenient way to store data that's used to drive behavior of your aggregates. It is not the most convenient way to present the current state of your system in your client applications such as web applications, mobile applications etc.

To transform your events to tailored projections or read-models you use the Projections client.

Configuring the Projections client

To create your first projection, first configure the client from the configuration you created before.

ProjectionClient projectionClient = 
    new ProjectionClient.Builder(serializedClientConfig).build();

This creates a Projections client that is ready to work with the project that the API keys in the Serialized configuration corresponds to.

Defining the projection

In this example you will configure a Projection that basically updates a field to keep the current status of an order so that a client can fetch the order and get the current status.

projectionClient.createOrUpdate(
        aggregatedProjection("order-status")
            .feed("order")
            .addHandler("OrderCreated",
                set().with(targetSelector("status")).with(rawData("CREATED")).build())
            .addHandler("OrderPlaced",
                set().with(targetSelector("status")).with(rawData("PLACED")).build())
            .addHandler("OrderCancelled",
                set().with(targetSelector("status")).with(rawData("CANCELLED")).build())
            .build());

The set() function is a JsonPath transformation function which sets a value for a key in the projection that you want to create. In this case it sets the status field to CREATED, PLACED and CANCELLED, respectively. In this case the rawData() function is used to set a hard-coded status but you can of course also access the data from the events to project that data. You can read more about what other functions are available in the API documentation.

After running this code you can check your projection in the Serialized Console Application. You should see something like this in the console.

If you successfully created a few in the step before in the step before you should also see some projection data already available for you.

The next step is to access this data via code using the Projection client.

Reading projection data

All projections are also available via our Projections API. Query the order status of the orders you created by calling the projectionsClient.query() method.

SingleProjectionQuery orderStatusQuery = 
    ProjectionQueries.single("order-status")
        .id(orderId)
        .build(Map.class);


ProjectionResponse<Map<String, Object>> response = 
    projectionClient.query(orderStatusQuery);

var status = response.data().get("status");
System.out.println("status = " + status);

Executing the code above for an existing order id should result in the following being printed on the console

status = CREATED

The Projections API and client supports a number of filtering and query options for different use cases. You can read more about the available operators in our API documentation. There is also support for defining custom projection functions, where you have full control over the code which you can write using the language of your choice.

Where to add the definition code?

Since the updates to the Projections API are idempotent, it is recommended to add the createOrUpdate() calls to the startup phase of your application. Serialized will re-create your projection data by streaming all events from the first event to the last to make sure all the data is matching the new definition.