gpt

这是一个结构清晰、目标明确、已经“可用级别”的事件系统,而且很明显你是有意识地在做:

下面我会按你关心的 3 个点逐一深入,然后再补充一些你现在没点名、但确实存在的隐患或可改进点。我会尽量站在“这是一个要长期维护的 Web 框架核心设施”的角度,而不是学院派。

一、你点名的 3 个问题

1️⃣ transform: ((Request) -> Request)? 的覆盖问题 —— 这是一个真实 BUG

你现在的模型是:

class RequestReceived(
    val request: Request,
    var transform: ((Request) -> Request)? = null
) : Event(false)

❌ 问题本质

这是一个典型的“单写槽位”设计错误

✅ 正确的抽象方式(推荐顺序)

✅ 方案 A(最推荐):可组合的变换链

class RequestReceived(
    var request: Request
) : Event(false) {

    private val transforms = CopyOnWriteArrayList<(Request) -> Request>()

    fun addTransform(transform: (Request) -> Request) {
        transforms += transform
    }

    internal fun applyTransforms() {
        for (t in transforms) {
            request = t(request)
        }
    }
}

框架内部在 emit 之后:

bus.emit(event)
event.applyTransforms()

优点

👉 这和 Koa / Express 中 middleware 对 request 的协作方式是等价的

⚠️ 方案 B:返回值聚合(不推荐)

on<RequestReceived> {
    return@on it.request.copy(...)
}

你会立刻遇到:

不适合事件系统

🔎 同样的问题存在于

ResponseGenerated(
    var transform: ((ResponseBody) -> ResponseBody)? = null
)

👉 两个地方都应该统一成“transform list”模型

2️⃣ var source: Colleen? 可空 —— 设计语义不自洽

你说得完全对:

用户在使用它时已被赋值了,不可能为空

而你现在的代码是:

var source: Colleen? = null
    internal set

❌ 实际问题

这是 Kotlin API 设计中的一个反模式

✅ 推荐改法(强烈)

方案 A:lateinit(最干净)

lateinit var source: Colleen
    internal set

并在 emit 入口 强制初始化

fun <T : Event> emit(event: T) {
    if (!::event.source.isInitialized) {
        event.source = owner
    }
    ...
}

语义:框架保证在 dispatch 前一定完成注入

这是 Kotlin 框架里非常常见的模式。

方案 B:构造期注入(更纯,但侵入性大)

sealed class Event(
    val source: Colleen,
    val bubbles: Boolean = true
)

❌ 缺点:

👉 不适合你现在的冒泡模型

✅ 结论

lateinit + internal set 是最符合你当前架构的解法

3️⃣ 事件是否冗余 / 是否遗漏(重点)

我会分 冗余遗漏 两部分说。

3.1 ❌ 明显冗余的事件

SubAppExecuting / SubAppExecuted

class SubAppExecuting(...)
class SubAppExecuted(...)

从事件语义上:

👉 这两个事件在“观察维度”上是冗余的

✅ 推荐做法

删除这两个事件,改为:

class MiddlewareNode(
    ...
    val kind: MiddlewareKind
)

enum class MiddlewareKind {
    NORMAL,
    SUB_APP
}

listener 想区分时:

on<MiddlewareExecuting> {
    if (it.node.kind == SUB_APP) { ... }
}

收益

⚠️ HandlerExecuting / HandlerExecuted 是否冗余?

这个 不算冗余,但可以考虑是否合并:

class HandlerEvent(
    val phase: Phase,
    val ctx: Context,
    val node: RouteNode,
    val duration: Long? = null
)

我个人倾向于 你现在的写法是 OK 的,因为:

👉 可以保留

3.2 ⚠️ 语义上“略显重复”的事件

ExceptionThrown vs ErrorHandled

class ExceptionThrown(...)
class ErrorHandled(...)

这个设计方向是对的,但现在的边界不够清晰。

建议你明确:

事件

含义

ExceptionThrown

有异常被抛出,但

尚未被处理

ErrorHandled

异常已被某个 error middleware / handler 消化

并保证:

否则监控/日志系统会很难判断状态。

3.3 ⚠️ 你现在“缺失但非常有价值”的事件

🚨 1️⃣ Route Matching 事件(你自己注释掉的)

你注释掉的这几行,其实是高价值事件

// RouteMatching
// RouteMatched
// RouteNotMatched

为什么值得保留?

👉 建议至少保留一个:

class RouteMatched(
    val ctx: Context,
    val node: RouteNode
) : Event()

🚨 2️⃣ RequestCompleted(最终态)

你现在最后一个是:

ResponseSent

但它缺少:

👉 建议补一个 终态事件

class RequestCompleted(
    val ctx: Context,
    val status: Int,
    val duration: Long,
    val exception: Exception? = null
) : Event(false)

这是:

最核心的一个事件

二、你没点名,但必须说的几个“隐患级”问题

⚠️ 1️⃣ 事件类型匹配过于“严格”

listeners[event::class.java]

这意味着:

on<Event> { ... } // ❌ 永远收不到

是否这是你“刻意设计的”?如果不是,你可能需要:

否则事件系统扩展性会被封死

⚠️ 2️⃣ 冒泡 + 可变 Event = 共享状态风险

你现在是:

这本身不是 bug,但你需要明确这是设计选择

“Event 是可变的、冒泡时是共享的”

否则将来你自己都会踩坑。

⚠️ 3️⃣ EventListener 未包含 priority(可能是刻意的)

你现在是 注册顺序即执行顺序

这是 OK 的,但你以后几乎一定会想要:

建议你至少 留一个扩展位

class EventListener<T : Event>(
    val eventType: Class<out Event>,
    val order: Int = 0,
    val handler: Consumer<T>,
)

哪怕现在不用。

三、总结(重点结论)

✅ 你现在设计中“确定有问题”的

  1. transform: ((Request) -> Request)?必须改为可组合

  2. source: Colleen?应该是 lateinit / 非空

  3. SubAppExecuting / Executed冗余,建议删除

⚠️ “方向对,但边界要收紧”的

🧠 我的整体评价

这是一个已经达到「框架级设计」而不是「玩具级」的事件系统
问题集中在 API 可组合性 & 语义精炼度,而不是方向错误。

如果你愿意,下一步我可以直接帮你:

你现在这个阶段,已经非常值得“精雕细琢”了。