-
Notifications
You must be signed in to change notification settings - Fork 14
Event Emitter Interfaces
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 thecreated
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 intoEvent
s and sends them to anEventOutChannel
. - To subscribe to events published by an
EventPublisher
after being recorded in anEventLog
. - To receive events replayed from an
EventSource
.
Examples of each are given below.
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);
});
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).
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);