领域事件与持久化 Outbox
Zenith 的跨领域可靠事件使用 PostgreSQL 持久化 Outbox。业务事实、事件快照和消费者回执必须在同一个事务中提交;事务回滚时不能留下事件,提交后进程退出也必须能由 worker 恢复。
发布
服务只能在持有业务事务时调用 publishEventInTx(tx, input)。发布输入必须包含显式 tenantId、固定事件版本、稳定 businessKey 和 orderingKey、已登记的 subjectRefs、不可变 payload、授权范围快照、发生时间和链路引用。
同一租户、事件类型和业务键只能表示一个事实。相同事实重试必须复用原事件;相同业务键对应不同负载必须失败。Outbox 不是资金、钱包、配额周期或其他业务幂等账本,长期幂等由领域表和约束负责。
事件快照必须包含消费者执行所需的历史值。消费者不能通过重新读取当前文件、当前流程配置或当前权限来重建过去事件的 payload。
消费者
消费者在注册表中使用稳定 consumerKey,声明匹配规则、依赖、执行 lane 和事务处理函数。依赖只能形成经过校验的静态顺序,不能引入通用可编辑 DAG。
消费者事务顺序为:验证租约、读取不可变事件、执行本地数据库效果或保存领域执行意图、写入结果、确认当前 generation 和 lease token。确认失败必须回滚同一事务中的效果。
回执状态为 pending、running、retrying、succeeded、skipped 或 dead。skipped 必须说明无目标、配置关闭、旧轮次或业务不适用原因;失败消费者单独重试,不能重置同一事件的其他成功消费者。
事件 worker 使用数据库批量认领、FOR UPDATE SKIP LOCKED、有限并发、租约、退避和轮询兜底。提交后的队列唤醒只是提示,丢失唤醒由轮询恢复。不要在事务内发送 pg-boss、HTTP、邮件、短信、支付渠道请求或 WebSocket。
外部交接
HTTP、支付渠道、PDF、文件和其他长任务由领域执行器完成。公共消费者只能在事务中创建领域意图或下游投递记录,并将意图引用写入回执。
外部请求必须带超时、租约和尝试历史。非幂等写请求在结果不明时进入查询或人工核查状态,禁止盲目重发。Webhook 在执行前重新检查订阅、应用、站点、空间和安全策略;接收方使用事件 ID 幂等。
通知使用 notifyWithin(tx, ...);审批卡片使用对应的 Within 写入函数。提交后的 WebSocket 只负责提示,不能替代持久化业务状态。
领域边界
- 支付和退款使用按订单号保序的
payment.local-settlement消费者,在一个本地事务中完成钱包、VIP、合同、优惠券、账本和手续费等适用步骤;分账和开放 Webhook 在本地结算确认后保存独立意图。 - 工作流权限回写、token 推进等保持同步事务钩子;通知、卡片、自动化首动作、节点监听、定义 Webhook 和归档分别由具名消费者交接。
- CMS 事件以真实发布激活版本为准;站点 Webhook 按站点授权分发,不能套用统一同租户过滤。
- Drive 活动在活动事务内冻结开放事件快照;IoT 告警、生命周期和 OTA 终态在源事务内发布。高频遥测和网关诊断仍是尽力数据。
domain_events是安全对象时间线摘要,不能替代完整 Outbox 负载。
清理与运维
event_outbox、消费者回执和尝试历史遵循 retention policy。清理运行记录不能删除订单、账本、钱包、流程实例、配额周期事实或安全审计数据。
Job Monitor 将公共事件积压、Webhook/通知投递、工作流作业、分账和 PDF 执行分开统计。事件详情、消费者、尝试、失败原因和下游引用必须按原业务权限展示;敏感密钥、token 和下载签名不能进入 payload 或日志。
新增事件必须同时补充共享事件定义、生产事务、消费者、租户/顺序/幂等测试、失败恢复测试、领域清单依赖和必要查询契约。旧总线、旧事件任务和旧 payload 适配器不再保留。