Files
atlas-seller-kit/docs/architecture/transactional-outbox-and-idempotency.md

5.2 KiB

幂等请求与 Transactional Outbox

状态:已确认的基础设施设计。对应 DDL 位于 sql/001_foundation.sql。首期只接入关键写操作和异步链路,不要求所有接口一次完成。

1. 分别解决什么问题

idempotency_requests 防止同一个用户请求被重复执行;典型原因是重复点击、浏览器超时重试或网络重放。它的结果是:同一个写请求只产生一份门店、发布任务或 AI 任务。

outbox_events 防止业务提交与队列投递之间出现空档。它的结果是:即使 API 在数据库提交后、BullMQ 入队前崩溃,Dispatcher 仍能从 PostgreSQL 找到待投递事件并补投。

两者互补但不互相替代:幂等解决“重复发起”,Outbox 解决“任务漏投递”。operations、发布任务、素材转存任务等业务表仍是用户可见的事实来源;Outbox 只是可靠投递机制。

2. 幂等请求的统一规则

仅对已认证的业务写接口使用本机制;注册、登录、刷新令牌等认证端点采用各自的限流和安全规则,不保存密码相关请求体。

2.1 调用与保存

客户端为一次逻辑操作生成 UUID 并放入请求头:

Idempotency-Key: 8f421c70-…

服务端在事务中以“工作空间、用户、HTTP 方法、规范化实际路径、键”查找或创建 idempotency_requests。request_fingerprint 是由服务端密钥计算的 HMAC,输入为规范化请求体和影响语义的请求头;不保存明文请求体,也不用普通哈希,以避免低熵敏感输入被离线猜测。

  • 同一键、相同指纹、记录已完成:返回已保存的 HTTP 状态和安全响应体,不重复执行。
  • 同一键、不同指纹:返回 409 IDEMPOTENCY_KEY_REUSED。
  • 同一键仍在执行:返回 409 IDEMPOTENCY_REQUEST_IN_PROGRESS,客户端稍后以同一键重试。
  • 业务事务回滚:幂等记录一并回滚,客户端可安全重试。

成功业务事务在写入资源、操作记录和 Outbox 事件后,将幂等记录标为 completed 并保存响应摘要。记录至少保留 24 小时;首期 DDL 默认 48 小时,由清理任务在过期后删除。

3. Outbox 的事务与投递

任何会启动异步工作的业务请求,必须在同一 PostgreSQL 事务内完成:

写入业务资源 / operation 或领域任务
            +
写入 outbox_events(待投递)
            +
完成 idempotency_requests(如该接口要求幂等)
            ↓ commit
Dispatcher 投递到 BullMQ
            ↓
Worker 执行业务工作并更新领域任务/operation

event_payload 仅保存事件处理所需的 ID、版本或不可变业务快照。Worker 再在受 RLS 约束的数据库事务内读取完整数据;不得把 API Key、Cookie、密码、对象存储永久密钥或预签名访问 URL 放入载荷或错误信息。

4. Dispatcher 与 Worker 规则

  1. Dispatcher 按工作空间处理待投递事件,并在每笔数据库事务中设置 app.workspace_id;应用角色不使用 BYPASSRLS。
  2. 领取事件时使用 FOR UPDATE SKIP LOCKED,将状态置为 dispatching,记录 locked_by 和有限的 locked_until。
  3. 成功调用 BullMQ 后标记 dispatched;以 outbox_events.id 作为 BullMQ jobId。若“已入队但尚未来得及标记”时进程崩溃,锁到期后会再次投递,因此投递语义是至少一次。
  4. 投递失败时增加 attempt_count,使用指数退避更新 next_attempt_at 并恢复 pending;超过规定次数时标记 dead_letter,保留脱敏错误供运维处理。
  5. Worker 必须可重入:以领域任务状态、平台侧幂等能力或自己的去重键阻止重复创建平台商品。不得把 BullMQ 的“只执行一次”当作正确性保证。
  6. 已投递事件不立即删除;保留一段时间用于排障,再由保留期清理任务删除。idempotency_requests 过期后也由独立清理任务删除。

5. 首批接入范围

场景 幂等 Outbox 说明
创建/替换门店授权、凭据验证 是 是 避免重复门店和漏掉验证任务。
上传完成后的媒体校验/处理 是 是 防止同一对象重复派生。
采集素材转存 是 是 HTTP 接收成功后可恢复执行转存。
首次上架发布 是 是 避免重复发布请求与发布任务丢失。
在线商品全量同步 是 是 同一门店同时只允许一个活跃同步任务。
AI 文本、类目或图片任务 是 是 避免重复调用和重复计费。
普通商品草稿保存 否(首期) 否 使用 revision / If-Match 处理并发即可。

6. 实施验收

  • 重放同一 Idempotency-Key 不产生第二份资源或第二个业务任务,并返回原响应。
  • 同一键但请求不同返回 409;两个并发相同请求仅一个进入业务执行。
  • 在“业务事务提交后、BullMQ 投递前”模拟进程崩溃,Dispatcher 能补投。
  • 模拟 BullMQ 已接受任务但 Dispatcher 未标记成功,重复投递不重复产生平台副作用。
  • 未设置工作空间 RLS 上下文时,应用角色无法读取或投递任一工作空间的 Outbox 事件。