Search code examples
scalalagom

How does event tagging work in Lagom?


Lagom reference documentation shows how to tag events:

object BlogEvent {
  val BlogEventTag = AggregateEventTag[BlogEvent]
}

sealed trait BlogEvent extends AggregateEvent[BlogEvent] {
  override def aggregateTag: AggregateEventTag[BlogEvent] =
    BlogEvent.BlogEventTag
}

The sealed trait suggest one can tag a parent Event to get all children been processed in order:

All events with a particular tag can be consumed as a sequential, ordered stream of events.

So we went this way, we tagged our parent event and we implemented one ReadSideProcessor using slick and it didn't worked. Increasing the logging level we saw an "unhandled message" and we found out the following in SlickReadSideImpl:

override def handle(): Flow[EventStreamElement[Event], Done, NotUsed] =
      Flow[EventStreamElement[Event]]
        .mapAsync(parallelism = 1) { element =>

          val dbAction = eventHandlers.get(element.event.getClass)
            .map { handler =>
              // apply handler if found
              handler(element)
            }
            .getOrElse {
              // fallback to empty action if no handler is found
              if (log.isDebugEnabled) log.debug("Unhandled event [{}]", element.event.getClass.getName)
              DBIO.successful(())
            }
            .flatMap { _ =>
              // whatever it happens we save the offset
              offsetDao.updateOffsetQuery(element.offset)
            }
            .map(_ => Done)

          slick.db.run(dbAction.transactionally)
        }

The eventHandlers.get(element.event.getClass) above fails to find any handler if the class doesn't exactly match the class for which the handler was registered, for example it is a subclass (our case).

This is somehow confusing: is this the desired behaviour or is it a bug in the implementation of the JDBCReadSideImpl and SlickReadSideImpl?

  • If it is the desired behaviour, then one should not tag a sealed trait event (and probably this need update in the documentation)
  • If is the desired behaviour, then JDBCReadSideImpl and SlickReadSideImpl cannot use a map from classname to handler.

Solution

  • This is working as intended. The expectation is that you will want to handle concrete event types in different ways, so you will register a handler for each concrete type.

    The point of tagging all of the event subtypes with the same tag is to ensure ordering between different types of events on the same entity. For example, if you have a BlogCreatedEvent and a BlogPublishedEvent then you would want to be sure that your processor receives the created event before the published event, even if it handles them differently.