Skip to content

Latest commit

 

History

History
193 lines (143 loc) · 6.74 KB

events.md

File metadata and controls

193 lines (143 loc) · 6.74 KB

Events

EDC provides an eventing system that permits to developers to write extensions that could react to events that are emitted from the core of the EDC and also emit custom events.

Subscribe to events

The entry point for event listening is the EventRouter component, on which an EventSubscriber can be registered.

Actually, there are two ways to register an EventSubscriber:

  • async: every event will be sent to the subscriber in an asynchronous way. Features:
    • fast, as the main thread won't be blocked during dispatchment.
    • not-reliable, as an eventual subscriber dispatch failure won't get handled.
    • to be used for notifications and for send-and-forget event dispatchment.
  • sync: every event will be sent to the subscriber in a synchronous way. Features:
    • slow, as the subscriber will block the main thread until the event is dispatched
    • reliable, an eventual exception will be thrown to the caller, and it could make a transactional context fail
    • to be used for event persistence and to satisfy the "at-least-one" rule.

The EventSubscriber is typed over the event kind (Class), and it will be invoked only if the type of the event matches the published one (instanceOf). The base class for all events is Event.

Extension example:

public class ExampleEventSubscriptionExtension implements ServiceExtension {
    @Inject
    private EventRouter eventRouter;

    @Override
    public void initialize(ServiceExtensionContext context) {
        eventRouter.register(Event.class, new ExampleEventSubscriber()); // asynchronous dispatch
        eventRouter.registerSync(Event.class, new ExampleEventSubscriber()); // synchronous dispatch
    }
}

Then the EventSubscriber subscription will receive all the events emitted from the EDC and react to them:

public class ExampleEventSubscriber implements EventSubscriber<Event> {
    
    public void on(EventEnvelope<Event> event) {
        // react to event    
    }
    
}

The EventEnvelope is used as a container for the Event itself. It will also have additional fields like

  • id: unique identifier of the event (set by default at a random UUID)
  • at: creation timestamp

To filter events, the classes of the events can be used. There are 5 "Intermediate superclasses" (AssetEvent, ContractDefinitionEvent, ContractNegotiationEvent, PolicyDefinitionEvent, TransferProcessEvent) of Event. Thus, in addition to filtering on a specific event, such as TransferProcessCompleted, it is possible to react to a group of events that generally have to do with Assets, ContractDefinition, ContractNegotiation, PolicyDefinition and TransferProcess.

In the example below the subscriber is interested in all events by using the type Event. In this case a manual filter with instanceOf the event kind is needed:

public class ExampleEventSubscriber implements EventSubscriber<Event> {
    
    public void on(EventEnvelope<Event> event) {
        var payload = event.getPayload();
        if (payload instanceof AssetCreated) {
            // react only to AssetCreated events
        }
    }
    
}

To subscribe a particular type of event, a specific class is needed like in this example:

public class ExampleEventSubscriber implements EventSubscriber<AssetCreated> {
    
    public void on(EventEnvelope<AssetCreated> event) {
        // Typed
        AssetCreated payload = event.getPayload();
    }
}

This works also for group of events using "intermediate superclasses" such as AssetEvent, ContractDefinitionEvent, etc.

The dispatcher will take care of calling the right subscribers based on their expressed type.

Emit custom events

It's also possible to create and publish custom events on top of the EDC eventing system. To define the event, extend the Event class.

Rule of thumb: events should be named at past tense, as they describe something that's already happened

public class SomethingHappened extends Event {

    private String description;

    public String getDescription() {
        return description;
    }

    private SomethingHappened() {
    }

    public static class Builder  {

        private SomethingHappened event;

        public static Builder newInstance() {
            return new Builder();
        }

        private Builder() {
            event = SomethingHappened();
        }

        public Builder description(String description) {
            event.description = description;
            return this;
        }

        public SomethingHappened build() {
            Objects.requireNonNull(event.payload.description);
            return event;
        }
    }
}

All the data regarding an event should be contained in the Event class.

As you may notice, we use the builder pattern to construct objects, as stated in the Architecture Principles document. The extended builder will inherit all the builder method from the superclass.

Once the event is created, it can be published it through the EventRouter component:

public class ExampleBusinessLogic {
    public void doSomething() {
        // some business logic that does something
        var event = SomethingHappened.Builder.newInstance()
                .description("something interesting happened")
                .build();

        var envelope = EventEnvelope.Builder.newInstance()
                .at(clock.millis())
                .payload(event)
                .build();
        
        eventRouter.publish(envelope);
    }    
}

Please note that the at field is a timestamp that every event has, and it's mandatory (please use the Clock service to get the current timestamp).

Serialization / Deserialization

By default, events must be serializable, because of this, every class that extends Event will be serializable to json by default (through the TypeManager service). The json will contain an additional field called type that describes the name of the event class. For example, a serialized EventEnvelope<SomethingHappened> event will look like:

{
  "type": "SomethingHappened",
  "at": 1654764642188,
  "payload": {
    "description": "something interesting happened"  
  }
}

To make such an event deserializable by the TypeManager, is necessary to register the type:

typeManager.registerTypes(new NamedType(SomethingHappened.class, SomethingHappened.class.getSimpleName()));

doing so, the event can be deserialized using the EvenEnvelope class as type:

var deserialized = typeManager.readValue(json, EventEnvelope.class);
// deserialized will have the `EventEnvelope<SomethingHappened>` type at runtime

(please take a look at the EventTest class for a serialization/deserialization example)