Vibe Coding 写入链路:把事务、幂等与副作用放进同一张时序图
AI 生成的“先查再写”在并发和超时下会重复扣款或发消息。本文从命令身份、唯一接受点、事务边界、Outbox 与未知结果恢复设计可重放写入链路。
本文要点
- 主题聚焦:AI、Vibe Coding、DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
“创建订单,如果存在就返回已有订单”常被 AI 实现为先 SELECT、没有再 INSERT。两个请求同时通过查询后仍会创建两行;如果数据库已经提交但响应丢失,客户端重试又会创建第三行。加一个事务也不自动解决重复,因为事务保证一组操作原子提交,不知道两次请求是否代表同一个业务命令。
API 幂等设计解释了重试安全的基本目标。本篇进一步把 Vibe Coding 的提示和审查单位从单个 handler 扩大到完整时序:客户端、API 实例、数据库、队列和第三方服务分别在何处失败,哪些动作可以重放。
先定义命令身份
幂等键必须由调用者在一次逻辑操作开始时生成,重试复用,同一用户的新操作换新键。服务端定义作用域,例如 (tenant_id, actor_id, operation, idempotency_key);只对全站 key 做唯一约束会让不同租户互相冲突,只按 key 而不含 operation 又会混淆不同端点。
接受幂等键时同时保存规范化请求摘要。相同 key、不同 payload 必须返回冲突,不能悄悄返回第一次结果。摘要应覆盖会改变语义的字段和 API 版本,排除 trace id 等传输噪声;规范化规则本身要版本化。HTTP 幂等键 Payload 绑定给出了更完整的并发接受点设计。
HTTP 方法的幂等语义也不能与业务幂等混为一谈。RFC 9110 把 PUT、DELETE 等定义为幂等方法,但一次 POST 也可借助应用层命令键安全重试;反过来,标成 PUT 并不会让发送邮件这种外部副作用自动去重。
用唯一约束决定唯一赢家
不要“先查是否处理过”,而是在事务里尝试插入命令记录,依靠唯一约束仲裁并发。记录至少包含作用域、payload hash、状态、资源 ID、稳定响应摘要和创建时间。冲突后读取已有记录:hash 不同则拒绝;已完成则返回保存的结果;仍处理中则返回明确的可轮询状态,或等待有界时间后再返回。
业务行和命令记录应在同一数据库事务提交。创建订单、写库存预留、关联命令结果任何一步失败都整体回滚。事务隔离的选择取决于不变量,不是越高越好;阅读PostgreSQL 事务与隔离级别后,把可能的写偏差转成唯一约束、条件更新或显式锁。AI 必须解释冲突如何表现,不能只生成 happy path。
UPSERT 可减少竞态窗口,但要逐项审查冲突目标和更新分支。数据库 UPSERT 模式中的 ON CONFLICT 只有在唯一键准确表达业务身份时才可靠。把所有冲突都解释为“已存在”会遮蔽其他约束失败;更新分支也不能覆盖第一次请求已经固定的金额或归属。
外部副作用不能塞进长事务
在数据库事务内调用支付、邮件或消息代理,会让数据库锁持有到网络返回;外部调用成功而本地事务回滚时仍会出现不一致。更稳妥的方案是在同一事务写业务数据与 Outbox 事件,提交后由 worker 投递。事件有稳定 event_id,消费者用 inbox/receipt 唯一约束去重。
“至少一次投递”意味着重复是正常输入,不是异常。消费者先声明 receipt,再做本地状态变更;若它还要调用无法原子参与事务的第三方,则把第三方请求键稳定绑定到 event_id,并保存远端结果。提供方不支持幂等键时,只能设计查询对账、人工确认或补偿,不能让 AI 宣称获得 exactly-once。
长流程要把每个可恢复步骤做成独立状态转换,长任务 Agent 持久化工作流的断点续跑方法同样适用。状态只能单向或按明确定义补偿迁移;worker 领取任务要有 lease 与超时恢复,进程崩溃后另一个 worker 可以继续。
专门测试“不知道成功没”
最重要的用例不是一次请求返回 201,而是故障点矩阵:插入命令前断连、业务写入后事务提交前崩溃、提交成功但响应前断连、Outbox 发布后未确认、消费者完成副作用后 receipt 响应丢失。对每个故障点重放同一个 key,最终业务资源、库存变化和外部动作都只能有一份可接受结果。
并发测试同时发送相同 key/相同 payload、相同 key/不同 payload、不同 key/同一业务自然键。前者只能一个请求成为创建者,第二种必须冲突,第三种按业务唯一规则决定合并还是拒绝。测试应连接真实数据库并保留事务隔离设置,mock repository 无法证明唯一约束仲裁。
观测指标区分首次接受、重复命中、payload 冲突、处理中等待、未知结果恢复、Outbox 延迟和消费者重复。日志只记录 key 的安全摘要,不记录完整敏感 payload。数据保留期要长于客户端可能重试的窗口;清理命令记录时,也要考虑业务自然键仍否阻止老请求复活。
给 AI 的最终任务不应是“为这个接口加幂等”,而应附上命令身份、唯一约束、时序图、外部副作用清单和故障矩阵,要求它逐个定位持久化点。事务解决一批本地写入是否一起发生,幂等解决同一意图被重复提交,Outbox 和 receipt 解决跨系统重放;三者缺一,链路都不能被称为可恢复。
Related