Open Credo

April 29, 2016 | Software Consultancy

The Concursus Programming Model: Under the Hood

In the previous two posts (part 1, and part 2), we looked at how Concursus uses method mapping to generate events from method calls on proxies, and to dispatch events to matching methods on event handlers and state class instances. This approach provides a concise, convenient client API to the Concursus event system; however the core of the system defines events and event-handling mechanisms without reference to any of the reflection-based machinery used to implement this API. It is perfectly possible (if comparatively cumbersome) to use a Concursus event store to read and write events without using reflection. In this post I’ll show how this is done, continuing with the “lightbulb” example introduced previously.

WRITTEN BY

Dominic Fox

Dominic Fox

The Concursus Programming Model: Under the Hood

First of all we define an EventType for a “lightbulb created” event, and a TupleSchema which defines the names and types of the parameters that can be associated with events of this kind. In this case there is only one parameter, “wattage”, which takes an integer:

EventType createdEventType = EventType.of("lightbulb", VersionedName.of("created", "0"));

TupleSchema lightbulbCreatedSchema = TupleSchema.of(
    "lightbulb/created_0",
    TupleSlot.of("wattage", int.class)
);

We also create a TupleKey which provides type-safe access to the “wattage” parameter, given a Tuple of parameters conforming to our schema:

TupleKey wattage = lightbulbCreatedSchema.getKey("wattage", int.class);

This is all the metadata we need to create a new Event, which can then be stored directly in our EventStore:

Event createdEvent = createdEventType.makeEvent(
        "id1",
        StreamTimestamp.now(),
        lightbulbCreatedSchema.make(wattage.of(60)));

InMemoryEventStore eventStore = InMemoryEventStore.empty();
eventStore.toEventOutChannel().accept(createdEvent);

When we want to read the Event back out of the EventStore, we need an EventTypeMatcher that can match event types up with TupleSchemas:

EventTypeMatcher typeMatcher = EventTypeMatcher.matchingAgainst(
    ImmutableMap.of(createdEventType, lightbulbCreatedSchema));

This is because event parameters are normally serialised (e.g. into JSON) in storage, and we need information about parameter types in order to deserialise them again; the TupleSchema is also used to verify that the stored parameter data matches our expectations. We supply this EventTypeMatcher to an EventSource when retrieving events (events whose type is not matched by the supplied EventTypeMatcher will be ignored):

EventSource eventSource = EventSource.retrievingWith(eventStore);
Event retrievedEvent = eventSource.getEvents(
    typeMatcher,
    AggregateId.of("lightbulb", "id1"))
    .get(0);

Finally, once we have retrieved our stored event from the event store, we can use the TupleKey we created earlier to read the “wattage” value from its parameters in a type-safe fashion:

assertThat(retrievedEvent.getParameters().get(wattage),
    equalTo(60));

All of the method mapping code we saw before is essentially a convenient way of defining EventTypes and TupleSchemas (based on method signatures), and converting method invocation arguments into Tuples in order to construct Events (and vice versa). We could if we chose use an entirely different mapping mechanism – for example, one based on bean-like POJOs with properties, getters and setters – to accomplish the same thing. In the next post, I’ll show how Kotlin’s sealed classes can be used to do just that, to provide a clean and convenient alternative API.

 

This blog is written exclusively by the OpenCredo team. We do not accept external contributions.

RETURN TO BLOG

SHARE

Twitter LinkedIn Facebook Email

SIMILAR POSTS

Blog