Home > Mobile >  Dynamically checking subclass relationship in Scala 3
Dynamically checking subclass relationship in Scala 3

Time:07-07

I am trying to port a solution for DomainEventHandlers and -Dispatcher from PHP8 to Scala 3. Handlers should specify a list of events they can handle (in a type-safe way, preferably, by their classes). Handlers are dynamically registered with the Dispatcher, which should aggregate a map from the elements of the lists from each Handler to a List of Handlers for those events.

When an event is raised with the Dispatcher, it should check the class of the current event against the keys from the map, and pass the event to each Handler in each list of Handlers for a key if and only if the event's class is identical to or a subclass of the class specified by the key.

In dynamically typed OOP languages like PHP8, this is easy - a Handler stores a list of class-names, which can be reified simply by [ClassName]::class, then the Dispatcher gets the event's class via $event::class and performs an is_a-check for each HashMap-key, which checks both exact match and subclass-relationship.

In Scala 3, I can't seem to find a good way to do this. Working with underlying Java-reflections via getClass or Class[?] produces problems due to the mismatch between the Scala and Java type-systems (specifically, trailing $ being either present or not). In Scala 2, Tags would probably have been the way to go - but Scala 3 reflection is a different beast, and I have not found a way to utilize it to implement the above, and would appreciate advice.

Concretely, let's say we have

trait DomainEvent[D1 <: Serializable, D2 <: Serializable, A <: Aggregate]
extends Event[D1, D2]:
  type AggregateType = A
  val aggregateIdentifier: (String, UUID)
  def applyAsPatch(aggregate: AggregateType): AggregateType

trait DomainEventHandler:
  val handles: List[???]
  def handle(event: DomainEvent[?, ?, ?]): ZIO[Any, Throwable, Unit]

object DomainEventDispatcher:
  val registeredHandlers: scala.collection.mutable.Map[???, List[DomainEventHandler]] = 
      scala.collection.mutable.Map()
  def registerHandler(handler: DomainEventHandler): Unit = ???
  def raiseEvent(event: DomainEvent[?, ?, ?]): ZIO[Any, Throwable, Unit] = ???

I am unsure what to use in place of ??? in the DomainEventHandler's List and the Dispatcher's Map - the registerHandler and raiseEvent-implementations will follow from that.

CodePudding user response:

Well, if your concrete event classes that you match aren't parametrized, it's pretty simple:

trait Event[A]
case class IntEvent(x: Int) extends Event[Int]
case class StringEvent(x: String) extends Event[String]

object Dispatcher {
   var handlers = List.empty[PartialFunction[Event[_], String]]
   def register(h: PartialFunction[Event[_], String]): Unit = { handlers = h :: handlers }
   def dispatch(event: Event[_]) = handlers.flatMap { _.lift(event) }
}

Dispatcher.register { case e: IntEvent => s"Handled $e" }
Dispatcher.register { 
   case e: IntEvent => s"Handled ${e.x}"
   case e: StringEvent => s"Handled ${e.x}"
}
Dispatcher.dispatch(new IntEvent()) // List(Handled 1, Handled IntEvent(1))
Dispatcher.dispatch(new StringEvent("foo")) // List(Handled foo)

But if you want to match on things like Event[Int], that makes things significantly more difficult. I wasn't able to find a good way to do it in (though, I am by no means an expert in scala 3 features). Not sure why they dropped ClassTag support ... I am taking at as a sign that matching on type parameters like this is no longer considered a good practice, and the "proper" solution to your problem is now naming all classes you want to match without type parameters.

  • Related