DTM Saga 的 Do/Undo 接口必须拆分为两个独立 HTTP 路径,因 DTM 仅通过 URL 路径识别子事务语义,复用路径或 query 参数会导致路由错误、补偿失败;Undo 需支持空回滚,URL 必须为绝对地址,gid 需用 uuid,接口返回必须含 result 字段,TransOut 与 TransIn 应分属不同服务边界,Do/Undo 均需幂等且不可依赖前序状态。

DTM Saga 的 Do/Undo 接口必须拆成两个独立 HTTP 路径
DTM 不识别“一个 handler 里用 query 参数区分 Do/Undo”的写法,它靠 URL 路径唯一标识子事务语义。把 /trans/do 和 /trans/undo 写在同一个 http.HandleFunc 里,DTM 重试或补偿时会路由错、调错逻辑,甚至跳过补偿。
- 正确做法:用不同路径注册两个 handler,例如
/v1/order/create(Do)和/v1/order/create_compensate(Undo) - 别用
if r.URL.Query().Get("op") == "undo"分支——DTM 不传这种参数,它只发 POST 到你声明的Compensate地址 - 即使单体部署,也要确保两个路径能被 DTM 独立发现和调用;用
http.ServeMux分路由没问题,但别复用函数名 - Undo 接口必须容忍“空回滚”:比如 Do 根本没执行成功,Undo 也得返回
{"result": "success"},否则 DTM 卡在 abort 状态
dtmcli.Saga 初始化时 URL 必须是绝对地址
DTM client 完全不拼接 URL,Action 和 Compensate 字段填相对路径(如 /api/trans/out)会导致请求发到 localhost:8080,而不是你的业务服务。这是新手最常踩的坑,错误现象是 DTM 日志显示 “HTTP 404” 或 “connection refused”,但你的服务明明在跑。
- 必须写成带协议和 host 的完整地址:
http://order-svc:8080/v1/order/create - host 不能写
localhost(本地调试除外),要填服务发现能解析的名,比如order-svc或 Kubernetes Service 名 -
gid务必用uuid.NewString()生成,别用时间戳或自增 ID——DTM 靠它做幂等去重,重复 gid 会导致事务被静默丢弃 - 所有接口返回体必须含
result字段且为字符串:{"result": "success"}或{"result": "failure"},DTM 只认这个字段判断成败
TransOut 和 TransIn 不能塞进同一个 Go HTTP server 实例
不是技术限制,而是 DTM 的事务建模要求:每个子事务必须有明确的服务边界。如果你把转账出账和入账都放在 http://localhost:8080 下,DTM 记录的子事务 ID 就会丢失服务粒度,Undo 时无法区分该调哪个逻辑分支,极易误补偿或漏补偿。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 本质是服务发现问题——DTM 把
http://trans-out-svc:8080/do和http://trans-in-svc:8080/do当作两个独立服务,URL 前缀参与日志追踪和重试上下文 - 硬要单体部署?至少用不同端口(如 :8081 和 :8082)+ 反向代理(Nginx / Traefik)暴露两个域名,让 DTM 看起来是两个服务
- 哪怕共用一个进程,也要用两个独立
http.ServeMux实例,分别监听不同端口,避免路由冲突和状态混淆 - 别在 Do 接口里直接
db.Commit()——Saga 是“先预留再确认”,真正落库要等 DTM 发来全局提交指令;提前 commit 会破坏协调逻辑,导致数据不一致
Do 接口必须幂等,且不能依赖前序步骤成功
DTM 会重试失败的 Do 步骤,但不会保证执行顺序——比如 TransOut 成功后 TransIn 失败,DTM 会重试 TransIn,此时 Do 接口若假设“出账一定已发生”,就可能重复扣款。Saga 的每个 Do 必须能独立成立、可重复执行。
立即学习“go语言免费学习笔记(深入)”;
- 典型做法:用唯一业务单号 + 数据库唯一索引拦截重复请求,或用状态机字段(如
status = 'created')做前置校验 - 不要在 Do 里查前一步数据库状态来决定是否执行——DTM 不保证原子性顺序,网络分区时可能看到“脏中间态”
- Undo 接口同样要幂等:同一笔
gid下多次调用/compensate,结果必须一致;建议用UPDATE ... SET balance = balance + ? WHERE gid = ? AND status = 'deducted'这类带条件的更新 - 常见错误是 Undo 写成“查余额 → 减去金额 → 更新”,这在并发下会超发;必须用原子 SQL 或分布式锁兜底
Saga 模式看着简单,真正落地时最难的不是写代码,而是把每个 Do/Undo 的边界、状态、幂等性、空回滚都想清楚。DTM 不帮你兜底业务逻辑,它只按你声明的 URL 和 result 字段机械执行——你给它什么契约,它就守什么契约。

















