Skip to content

Event Emitter Interfaces

Dominic Fox edited this page Apr 27, 2016 · 3 revisions

An event emitter interface is a Java interface that follows a a set of conventions which enable method calls to be translated into Concursus events. The interface as a whole is bound to a particular aggregate type, and each method of the interface defines a single event that can occur to aggregates of that type.

@HandlesEventsFor("person")
public interface PersonEvents {
    @Initial
    void created(StreamTimestamp ts, String personId, String name, LocalDate dateOfBirth);

    void changedName(StreamTimestamp ts, String personId, @Name("newName") String name);

    @Name(value = "movedToAddress", version = "1")
    void changedAddress(StreamTimestamp ts, String personId, String addressId);

    @Terminal
    void deleted(StreamTimestamp ts, String personId);
}

The above interface is annotated with @HandlesEventsFor, which binds it to the aggregate type "person". Each event-emitting method has the following characteristics:

  • It returns void - emitting an event is a "fire and forget" operation, if it fails then an unchecked exception will be thrown.
  • The method name is used as the name of the event, unless the method has a @Name annotation giving an overriding name. Event names are always versioned, with a default version of "0", so by default the created method will create an event of type "created_0".
  • The first parameter is a StreamTimestamp, which is the "event timestamp" stating when, and as part of what stream of events, the event occurred.
  • The second parameter is a String, which is the unique identifier of the aggregate to which the event occurred.
  • The remaining parameters are converted into a Tuple of typed name/value pairs, which is stored as the event's parameters. Parameters may be annotated with @Name to override the use of the Java parameter name.

The @Initial and @Terminal annotations indicate that the events emitted by the annotated methods are the first and last things that can happen to an aggregate of that type.

Event emitter interfaces are used for three purposes:

  • To emit events, by creating an EventEmittingProxy which translates method calls against the interface into Events and sends them to an EventOutChannel.
  • To subscribe to events published by an EventPublisher after being recorded in an EventLog.
  • To receive events replayed from an EventSource.

Examples of each are given below.

Emitting events

To emit the events defined in an event emitter interface, we obtain an EventEmittingProxy connected to an EventOutChannel:

// Create an EventOutChannel that simply prints events to the command line
EventOutChannel outChannel = System.out::println;

// Create a proxy that sends events to the outChannel.
PersonEvents proxy = EventEmittingProxy.proxying(outChannel, PersonEvents.class);

// Send an event via the proxy.
proxy.created(StreamTimestamp.now(), "id1", "Arthur Putey", LocalDate.parse("1968-05-28"));

This will output a String like the following:

person:id1 created_0
at 2016-03-31T10:31:17.981Z/
with person/created_0{dateOfBirth=1968-05-28, name=Arthur Putey}

Alternatively, if using an EventBus we can obtain a ProxyingEventBus that allows us to batch events together:

ProxyingEventBus proxyingEventBus = ProxyingEventBus.proxying(eventBus);
proxyingEventBus.dispatch(PersonEvents.class, e -> {
    e.created(timestamp, personId, "Arthur Putey", LocalDate.parse("1968-05-28"));
    e.changedName(timestamp.plus(1, MINUTES), personId, "Arthur Mumby");
    e.deleted(timestamp.plus(2, MINUTES), personId);
});

Subscribing to events

When using the Concursus Spring beans, any component annotated with @EventHandler will automatically be registered to respond to events published by the system. Any such component should implement a single event emitter interface, and will be called when events matching its methods are published.

Here's an example in which event subscription is wired up manually:

SubscribableEventPublisher eventPublisher = new SubscribableEventPublisher();
PersonEvents personEventHandler = mock(PersonEvents.class);
AddressEvents addressEventHandler = mock(AddressEvents.class);

DispatchingSubscriber dispatchingSubscriber = DispatchingSubscriber.subscribingTo(eventPublisher);
dispatchingSubscriber.subscribe(PersonEvents.class, personEventHandler);
dispatchingSubscriber.subscribe(AddressEvents.class, addressEventHandler);

PersonEvents personEvents = EventEmittingProxy.proxying(eventPublisher, PersonEvents.class);
AddressEvents addressEvents = EventEmittingProxy.proxying(eventPublisher, AddressEvents.class);

Events emitted via the proxies will be routed to the subscribed handler objects (mocked in this example).

Receiving events from an EventSource

If we have an EventSource, we can obtain a DispatchingEventSource that will dispatch replayed events to a handler implementing the appropriate event emitter interface.

PersonEvents personEventHandler = mock(PersonEvents.class);

DispatchingEventSource.dispatching(eventSource, PersonEvents.class)
    .replaying(personId).
    .replayAll(personEventHandler);