事件必须声明为不可变类并固化契约,禁止原型置换;动态数据应封装进值对象而非替代事件;跨上下文需通过映射层转换并校验schema;事件演进须走版本化流程与变更评审。

原型置换本身不是问题,问题出在它被当作“快捷建模工具”用在了不该抽象的环节——尤其是事件派发这类需严格契约约束的边界行为。
明确事件契约,禁止在事件定义层做原型置换
事件本质是领域间通信的协议,不是可随意替换的数据结构。一旦允许用原型动态生成事件类型(如用 Map 或 JSON 模拟 Event 对象),就等于放弃编译期校验、序列化一致性与上下游语义对齐。
- 所有事件必须声明为不可变类(如 C# record / Java sealed class / TypeScript interface + readonly),字段名、类型、版本号全部固化
- 禁止在事件构造器、发布逻辑或仓储接口中接受泛型参数或运行时传入的“模板对象”
- 事件命名须含业务动词+上下文(如 PaymentConfirmedInUsd 而非 GenericEvent)
将原型逻辑下推到值对象或DTO层,与事件解耦
若确实需要动态数据承载(如用户填写的任意表单字段),应封装进专用值对象(Value Object),作为事件的一个字段存在,而非替代事件本身。
- 例如:订单创建事件 OrderPlaced 可包含一个 CustomFormData 字段,该字段内部用 Map 存储键值对,但事件类型、生命周期、发布时机完全独立
- 值对象需有明确校验规则(如 key 长度 ≤ 64,value 类型限于 string/number/boolean),且不参与事件溯源重放逻辑
- 避免在事件处理器中反序列化并“还原”原型,所有消费方只读取已定义字段
用限界上下文边界切断原型传播链
原型置换失控常源于跨上下文传递未收敛的模型。不同上下文对同一概念的理解本就不一致,强行用一个“万能原型”去桥接,只会放大语义漂移。
- 每个限界上下文对外暴露的事件,必须经过上下文映射层转换,将内部原型结构翻译为对方契约认可的确定性类型
- 禁止跨上下文直接共享 domain event 类库;发布方只输出标准化 JSON Schema,订阅方按 Schema 构建本地事件实体
- 在 API 网关或事件总线接入点部署 schema 校验中间件,拦截字段缺失、类型错配、未知字段等违规事件
建立事件演进治理机制,替代临时原型方案
团队真正需要的不是“灵活”,而是“可控的演进”。当业务要求新增字段时,应走版本化事件升级流程,而非现场拼凑原型。
- 事件类型采用语义化版本(如 OrderPlaced-v2),旧版本继续支持至少两个迭代周期
- 引入事件变更评审卡(Event Change Card),强制记录变更原因、影响范围、兼容性策略,由领域专家+架构师双签确认
- 自动化工具扫描代码中所有
instanceof或getClass()判断,标记出隐式依赖原型行为的“脆弱消费方”











