You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 14:51:21