Skip to content

Upcasting Events

Events may evolve over time, requiring them to be migrated. As they are immutable, they cannot be altered within the event store. Event upcasting resolves this enabling the developer to migrate events on-demand, that is in-memory, whenever an older version of an event is loaded.

Implementations of EventUpcaster may be registered with the EventRepository to migrate raw Events prior to mapping them to their appropriate Java event type.

Custom Upcasters

Custom EventUpcasters should be implemented by extending AbstractEventDataMarshallingEventUpcaster. This relieves the developer from dealing with the raw { javadoc_class_ref("com.opencqrs.esdb.client.Event") }} data and instead accessing the unmarshalled payload and metata directly.

The following example shows how to implement an upcaster, which upcasts events of type com.opencqrs.library.book.purchased.v1 to com.opencqrs.library.book.purchased.v2 by renaming the payload attribute name to surname:

import com.opencqrs.esdb.client.Event;
import com.opencqrs.framework.serialization.EventDataMarshaller;
import com.opencqrs.framework.upcaster.AbstractEventDataMarshallingEventUpcaster;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

public class BookPurchasedEventUpcaster extends AbstractEventDataMarshallingEventUpcaster {

    protected BookPurchasedEventUpcaster(EventDataMarshaller eventDataMarshaller) {
        super(eventDataMarshaller);
    }

    @Override
    public boolean canUpcast(Event event) {
        return event.type().equals("com.opencqrs.library.book.purchased.v1");
    }

    @Override
    protected Stream<MetaDataAndPayloadResult> doUpcast(Event event, Map<String, ?> metaData, Map<String, ?> payload) {
        Map<String, Object> newPayload = new HashMap<>(payload);
        newPayload.put("surname", payload.get("name"));
        newPayload.remove("name");

        return Stream.of(
                new MetaDataAndPayloadResult(
                        "com.opencqrs.library.book.purchased.v2",
                        metaData,
                        newPayload
                )
        );
    }
}

Avoiding Infinite Recursion

As there will be multiple EventUpcasters within an application over time, it is essential to understand, that these will be applied to any event that they canUpcast. Accordingly, it is up to the developer to avoid infinite recursion, for instance by returning the same unaltered event type or by providing different EventUpcasters effectively switching types.

Built-In Upcasters

OpenCQRS provides the following built-in EventUpcaster implementations:

  • NoEventUpcaster which evicts an event matching the configured type, effectively removing it from the event stream with respect to the application code
  • TypeChangingUpcaster which only changes the event's type, assuming no other changes need to be applied

Registration

EventUpcaster instances can be defined as Spring Beans within the application context (1), for instance within a dedicated OpenCqrsFrameworkConfiguration, as follows:

  1. Refer to manual configuration if you want to register it manually with the EventRepository.
package com.opencqrs.example.configuration;

import com.opencqrs.framework.serialization.EventDataMarshaller;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

@Configuration
public class OpenCqrsFrameworkConfiguration {

    @Bean
    public BookPurchasedEventUpcaster bookPurchasedEventUpcaster(EventDataMarshaller eventDataMarshaller) {
        return new BookPurchasedEventUpcaster(eventDataMarshaller);
    }
}

Tip

Alternatively, EventUpcaster implementations may simply be annotated using Spring's @Component annotation to be recognized as part of Spring's classpath component scanning.