Scala 3如何动态检查子类关系实现领域事件分发器
Scala 3 领域事件分发器实现方案
两处???统一替换为scala.reflect.Typeable[? <: DomainEvent[?, ?, ?]]即可。Typeable是Scala 3标准库提供的类型安全运行时类型标记,由编译器自动为所有Scala类型生成实例,原生支持精确类型匹配+子类继承关系判断,完全规避Java原生反射的类名$后缀混淆、类型擦除匹配错误等问题,是Scala 2中TypeTag能力的官方拆分替代方案。
完整实现代码
import scala.reflect.Typeable import zio.ZIO import java.util.UUID import scala.collection.mutable // 基础接口定义(与原有骨架对齐) trait Event[D1 <: Serializable, D2 <: Serializable] trait Aggregate 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[Typeable[? <: DomainEvent[?, ?, ?]]] def handle(event: DomainEvent[?, ?, ?]): ZIO[Any, Throwable, Unit] object DomainEventDispatcher: // 事件类型 -> 对应处理器列表的映射表 private val registeredHandlers: mutable.Map[Typeable[? <: DomainEvent[?, ?, ?]], List[DomainEventHandler]] = mutable.Map.empty // 动态注册处理器 def registerHandler(handler: DomainEventHandler): Unit = handler.handles.foreach { eventType => val existing = registeredHandlers.getOrElse(eventType, Nil) registeredHandlers.update(eventType, handler :: existing) } // 上报事件并匹配派发 def raiseEvent(event: DomainEvent[?, ?, ?]): ZIO[Any, Throwable, Unit] = ZIO.foreachDiscard(registeredHandlers.toList) { case (eventType, handlers) => // cast方法原生支持子类判断,匹配成功返回Some(目标类型事件),失败返回None eventType.cast(event) match case Some(matched) => ZIO.foreachDiscard(handlers)(_.handle(matched)) case None => ZIO.unit }
使用方式
编译器会自动为所有自定义事件生成Typeable实例,不需要手动编写反射逻辑:
// 辅助方法简化类型标记声明 inline def eventType[T <: DomainEvent[?, ?, ?]]: Typeable[T] = summon[Typeable[T]] // 自定义事件样例 case class User(id: UUID, username: String) extends Aggregate case class UserCreated(userId: UUID, username: String) extends DomainEvent[Nothing, Nothing, User]: override val aggregateIdentifier: (String, UUID) = ("user", userId) override def applyAsPatch(aggregate: User): User = aggregate.copy(username = username) // 自定义处理器样例 class UserEventHandler extends DomainEventHandler: override val handles: List[Typeable[? <: DomainEvent[?, ?, ?]]] = List(eventType[UserCreated]) override def handle(event: DomainEvent[?, ?, ?]): ZIO[Any, Throwable, Unit] = val userCreated = event.asInstanceOf[UserCreated] ZIO.log(s"处理用户创建事件,用户名:${userCreated.username}")
行为说明
- 匹配逻辑与PHP的
is_a完全一致:处理器声明处理父类事件时,所有子类事件上报都会被匹配派发;声明处理具体事件时,仅该类及其子类事件会被派发。 - 自动规避Java反射坑:不管是普通类、样例类、单例object事件,编译器生成的
Typeable实例都会做正确的类型判断,不会出现$后缀导致的匹配失败。 - 可扩展性强:如果需要对事件的泛型参数(如
D1/D2类型)做更严格的校验,可以搭配Scala 3的Quote宏在注册阶段做编译期检查,绝大多数业务场景下Typeable的类层级匹配已经满足需求。
内容的提问来源于stack exchange,提问作者MBauerDC
相关产品推荐
相关产品推荐

