Files
junhong_cmp_fiber/openspec/changes/archive/2026-08-13-reliable-order-commission-dispatch/design.md
2026-08-13 17:26:36 +08:00

3.7 KiB
Raw Blame History

Context

当前订单服务在事务提交后直接向 Asynq 提交 commission:calculate;退款服务在审批事务提交后使用裸 goroutine 回扣佣金及处理资产。两者均不与业务事实绑定进程、Redis 或 Worker 短暂异常会分别遗留待计算订单或 commission_deducted=false 的已退款单。现有 Outbox 已具备状态、租约、重试与任务投递能力;见 proposal.md。

Goals / Non-Goals

Goals:

  • 将订单佣金计算请求纳入现有 Outbox 可靠投递链路。
  • 为遗留待计算订单提供有界、幂等的补偿入口。
  • 为遗留退款后处理提供有界、幂等的补偿入口。
  • 保持现有佣金计算与订单结果兼容。

Non-Goals:

  • 不改变佣金金额、分配规则或提现逻辑。
  • 不引入新队列、中间件或外部依赖。
  • 不自动修改已完成、待人工修正或未支付订单。
  • 不改变退款金额、审批结论和既有资产后处理业务规则。

Decisions

使用订单级稳定事件 ID 写入现有 Outbox

订单创建/支付成功的同一数据库事务写入 order.commission.calculate 事件,业务键由订单 ID 派生,建立唯一约束或既有去重语义。选择 Outbox 而不是事务后重试 Asynq因为事件能与已支付订单原子落库并具备失败状态。

Relay 投递现有佣金任务类型

新增 Outbox 消费者仅将结构化订单 ID 投递给既有 commission:calculate,不改佣金计算服务。选择复用任务处理器,避免并行的计算实现和金额语义分叉。

有界扫描补偿历史待计算订单

Worker 启动或既有调度器以分页上限扫描已支付、CommissionStatusPending 的订单;缺事件或终态失败事件时按同一业务键补写/恢复事件。选择可重复扫描而非一次性数据库修复,方便部署中断恢复;扫描只恢复投递,不直接计算。

退款后处理使用退款单级 Outbox 事件

退款审批成功的同一事务分别写入佣金回扣与资产后处理事件,业务键按退款单和处理类型派生。消费者调用既有幂等后处理函数。选择两个独立事件以保留“佣金已回扣但资产重置待处理”的现有可观察状态;不把长资产操作放进审批事务。

有界扫描补偿已退款未完成单

Worker 按分页上限扫描已退款且 commission_deducted=falseasset_reset=false 的退款单,并按稳定业务键恢复缺失或终态失败事件。选择状态驱动扫描而非单次修数,确保已退款订单在部署或 Worker 中断后仍可恢复。

以既有终态判断实现消费幂等

佣金服务已对已完成和待人工修正订单跳过。补偿和消费者只产生同一业务键事件,佣金记录仍由现有计算事务落库,防止重复余额发放。

Risks / Trade-offs

  • [同一订单存在旧直投与新 Outbox 任务] → 依赖既有佣金终态短路和记录事务,部署期间允许重复请求。
  • [补偿扫描加载过多历史数据] → 固定批量上限、按状态和支付状态筛选,并输出扫描计数与失败日志。
  • [事件定义/消费者漏注册] → Worker 启动时注册并在构建与手工测试中验证事件由 Relay 投递。
  • [退款审批与旧 goroutine 并发] → 新事件和既有回扣函数均以退款标记、佣金状态与回扣流水去重,部署过渡允许重复请求。

Migration Plan

  1. 先发布带事件类型、消费者和补偿扫描的新 Worker/API 版本。
  2. 部署后执行订单与退款补偿扫描,核对待计算订单、已退款未回扣退款单、投递状态及资金结果。
  3. 若需回滚代码,停止补偿扫描;已持久化事件保留,恢复新版本后继续投递,不回滚订单、退款或佣金事实。