Open Credo

April 29, 2016

The Concursus Programming Model: Kotlin

In this post, I’ll demonstrate an alternative API which uses some of the advanced language features of the new Kotlin language from Jetbrains. As Kotlin is a JVM-based language, it interoperates seamlessly with Concursus’s Java 8 classes; however, it also offers powerful ways to extend their functionality.

WRITTEN BY

Dominic Fox

Dominic Fox

The Concursus Programming Model: Kotlin

In my last post on Concursus, I showed how an Event is constructed using a TupleSchema which represents the names and types of the event’s expected parameters, and how an EventTypeMatcher is used to map serialised event data back into the Java type system. The reflection-based method mapping demonstrated in the previous two posts (part 1 and part 2) builds on these mechanisms to provide a convenient Java 8 API for emitting and receiving events.

In this post, I’ll demonstrate an alternative API which uses some of the advanced language features of the new Kotlin language from Jetbrains. As Kotlin is a JVM-based language, it interoperates seamlessly with Concursus’s Java 8 classes; however, it also offers powerful ways to extend their functionality.

Defining events using sealed classes

A sealed class in Kotlin is an abstract class that can only be extended by subclasses defined as nested classes within the sealed class itself – a kind of souped-up Enum. This means that a compiler can always tell whether an expression which matches objects of the sealed class by type covers all of the possible cases. Sealed classes are ideal for defining algebraic data types, such as the following:

When we want to write an operation on BinaryTree<T>, we can use Kotlin’s when syntax to match against its nested types:

This suggests an alternative “encoding” for a collection of events addressed to the same type of object:

A handler for LightbulbEvents could then use the where syntax to match on the type of event it had received:

In fact, we need a little more information to represent a complete Concursus event:

but we now no longer have to depend on the convention that event method definitions (in the Java 8 API) must have timestamp and id values for their first two parameters.

Using a companion object to create events

Companion objects enable us to add the equivalent of Java’s static methods to a Kotlin class, with the added advantage that companion objects can be passed around as objects in their own right. We can equip the LightbulbEvent class with a create method that returns a Concursus Event like so:

We can make use of the fact that companion objects can be passed around, to create a function that collects multiple events into a batch:

Here, the companion object which provides LightbulbEvent‘s “static” factory method, also functions as a factory object that KEventWriter can use to create new events. Better still, we can use Kotlin’s support for function literals with receivers to create a function that takes a block in which the KEventWriter which adds events to the batch is the implicit receiver of every write method invocation. This results in a syntax with the fluency of a “method-chaining fluent API”, but without the need to actually chain methods together.

Using data classes for immutable state

The Java 8 implementation of state classes in Concursus routes events to methods which mutate an object. In Kotlin, we can create a fully immutable alternative using data classes. To begin with, we need a way to define a state transition as a function of a possibly existing state and an event:

(We cheat a little on immutability here by using a mutable workingState variable inside runAll, instead of defining a fold over the list of events). The update method determines whether an event should be considered as an “initial” event that might create a new state from nothing, or a “next” event that creates a new state from an existing state; a class implementing Transitions must provide implementations of both initial and next. We now create such an implementation for a data class representing the state of a lightbulb:

Each transition creates a copy of the current data class instance, selectively replacing the values of updated fields. We can now play a series of events to the LightbulbState class, and get back an instance which we can use to calculate the power consumption of the lightbulb to date:

Note that we first have to map our Concursus Events into KEvents, so that the transition code can interpret them. Here we do this with an extension method added to Event – which brings us to one final trick Kotlin can perform for us…

From wrappers to extension methods

In Concursus’s Java 8 code, core classes such as Event and EventSource are defined without reference to any mapping layer, which means that when we want an EventSource that knows how to map event types to methods on an interface and dispatch them to a handler object, we have to create a DispatchingEventSource object that wraps an existing EventSource:

This isn’t an enormous hardship, but it’s even nicer to be able to define an extension method in Kotlin that is directly added to EventSource, so that we can simply write:

In this way, “outer” modules can progressively layer functionality on top of “inner” modules, adding a sprinkle of reflection-based magic to the more pedestrian core implementation.

While Kotlin clearly has a lot to offer us here, the modular design of Concursus means that it should be straightforward to build custom APIs making use of the syntax and idioms of other JVM languages, such as Scala, Clojure and Ceylon.

RETURN TO BLOG

SHARE

Twitter LinkedIn Facebook Email

SIMILAR POSTS

Blog