/ EXERCISE, PROGRAMMING, STYLE

Exercises in Programming Style and the Event Bus

In last week’s post, we solved the now familiar top-25-word-frequencies-in-a-text-file problem by using Event-Driven Programming.

This is the 10th post in the Exercises in Programming Style focus series.Other posts include:

  1. Introducing Exercises in Programming Style
  2. Exercises in Programming Style, stacking things up
  3. Exercises in Programming Style, Kwisatz Haderach-style
  4. Exercises in Programming Style, recursion
  5. Exercises in Programming Style with higher-order functions
  6. Composing Exercises in Programming Style
  7. Exercises in Programming Style, back to Object-Oriented Programming
  8. Exercises in Programming Style: maps are objects too
  9. Exercises in Programming Style: Event-Driven Programming
  10. Exercises in Programming Style and the Event Bus (this post)
  11. Reflecting over Exercises in Programming Style
  12. Exercises in Aspect-Oriented Programming Style
  13. Exercises in Programming Style: FP & I/O
  14. Exercises in Relational Database Style
  15. Exercises in Programming Style: spreadsheets
  16. Exercises in Concurrent Programming Style
  17. Exercises in Programming Style: sharing data among threads
  18. Exercises in Programming Style with Hazelcast
  19. Exercises in MapReduce Style
  20. Conclusion of Exercises in Programming Style

A bit of theory

The Observer pattern is a messaging pattern: an Observer subscribes to a Subject. When the later emits an event, the former is notified. This is Point-to-Point messaging: if multiple observers are interested in an event, they all must subscribe to the subject.

Point-to-point messaging comes with with a huge downside: each observer needs to reference each relevant subject at one point or another. This exponentially increases the complexity when the number of observer-subject pairs grows.

Another way to do messaging is Publish-Subscribe. In that case, messages are sent to a queue. Subscribers can register to a specific queue, to be notified of messages sent to that queue. As an option, messages can be persisted, so that subscribers registering to a queue can be notified of messages sent prior to the registration.

The Event Bus is an implementation of the Publish-Subscribe pattern, where there’s a single dedicated queue for each event type. Here’s the class diagram:

Event bus class diagram

You can read more about it in this previous post.

Modeling the event bus

As with the Observer pattern from last week, an event handler is a simple function, wrapped in a lambda.

The event bus, named EventManager in the Python code, needs:

To register event-handlers

This associates a lambda with an event type

To handle events

This invokes lambdas associated with an event type when it’s passed an event

The model looks something like the following:

Modeling the Event Bus in Kotlin

Note that there’s a single generic event class with a Type attribute. Alternatively, one could design a subclass per event type.

Here’s how to use the bus:

class DataStorage(private val eventManager: EventManager) {        (1)

  private lateinit var data: List<String>

  init {
    eventManager.subscribe<String>(Load) { load(it) }              (2)
    eventManager.subscribe<Unit>(Start) { produceWords() }         (2)
  }

  private fun load(event: Event<String>) {
    if (event.payload != null) {
      data = read(event.payload)
        .flatMap { it.split("\\W|_".toRegex()) }
        .filter { it.isNotBlank() && it.length >= 2 }
        .map(String::toLowerCase)
    }
  }

  private fun produceWords() {
    for (word in data) {
      eventManager.publish(Event(Word, word))                      (3)
    }
    eventManager.publish(Event(EOF))                               (3)
  }
}
1 Embed the event manager. Each class that wants to take part in the same event management system needs to use the same event manager instance.
2 Associate event handlers under a specific event type. The subscribe() function allows a generic type that matches the payload of the event. Payload-less events use Unit.
3 Send a specific event, triggering the call of the stored lambda

Not reinventing the wheel

The event manager could easily be made generic enough so that it could be reused across different projects. However, as a developer, I’m lazy so I prefer to use an existing library if there’s one, even if it goes beyond the scope of the exercise. The good news is that there are different Event Bus libraries out there that work really great:

Among them, I used - and liked - Guava’s. Guava makes the process of registering and sending events a breeze. Here’s the same code as above ported to Guava:

class DataStorage(private val eventBus: EventBus) {

  private lateinit var data: List<String>

  init {
    eventBus.register(this)                             (1)
  }

  @Subscribe                                            (2)
  private fun load(event: LoadEvent) {                  (3)
    data = read(event.filename)
      .flatMap { it.split("\\W|_".toRegex()) }
      .filter { it.isNotBlank() && it.length >= 2 }
      .map(String::toLowerCase)
  }

  @Subscribe                                            (2)
  private fun produceWords(event: StartEvent) {         (3)
    for (word in data) {
      eventBus.post(WordEvent(word))                    (4)
     }
   eventBus.post(EOFEvent)                              (4)
  }
}
1 Compared to our code above, registration is quite simple: instead of registering handlers one by one, register() scans for all functions of the class annotated with @Subscribe.
2 A function annotated with @Subscribe is considered to be an event-handler function. It must accept a single parameter, and return nothing.
3 The parameter’s type plays the role of EventManager.Type in our custom implementation. One needs a dedicated type for each event.
4 Posting is also straightforward: create an instance of the event - or use a singleton object for events with no payload - and send it to the event bus with the post() function

Conclusion

Messaging comes into two flavors: on one hand, the Observer pattern is Point-to-Point, and its complexity grows with the number of observer-subject pairs as the observer needs to get a reference on the relevant subject. On the other hand, the Event Bus has Publish-Subscribe semantics. It allow to introduce an intermediate component - the Event Bus - between the observer-subject pair, so that each only needs a reference to the bus.

While it’s perfectly possible to implement one’s own implementation of the Event Bus, it’s easier to use an existing battle-hardened one. It’s out of the scope of the exercise, but Guava’s EventBus is a solid library in that regard.

The complete source code for this post can be found on Github.
Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Hazelcast. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More