Go用go+channel可实现轻量级工作流编排,无需第三方引擎;核心是统一func(*WorkflowContext)error签名、结构体封装上下文、显式错误中断与补偿逻辑,并通过接口隔离依赖以保障可测试性。

Go 里用
go + channel 实现轻量级工作流编排,别碰第三方引擎
Go 自身的并发原语已经足够支撑多数任务编排场景。硬上 temporal 或 cadence 往往是过度设计——尤其当你只是想串起几个 HTTP 调用、数据库操作和条件判断时。真正需要的是清晰的状态流转和错误传播机制,不是分布式协调能力。
- 工作流本质是「有向无环图」,但多数业务流程线性或带简单分支,用
func() error切片 + 顺序执行更直观 -
channel适合传递中间结果或触发信号,但别用它做状态机;状态该存结构体字段就存字段 - 每个步骤应返回
error,且上游必须检查;忽略err != nil是工作流静默失败的头号原因
用 struct 封装上下文,避免全局变量和闭包捕获陷阱
传参混乱是 Go 工作流最常崩的点。比如在 for 循环里启动 goroutine,却把循环变量 i 直接闭包捕获,结果所有步骤都拿到最后一个值。
- 定义一个
WorkflowContext结构体,把需要跨步骤共享的数据(如userID、reqID、timeoutCtx)全塞进去 - 所有步骤函数签名统一为
func(*WorkflowContext) error,不依赖外部闭包或包级变量 - 不要把
<em>http.Client</em>或sql.DB塞进 context;它们是依赖,该通过构造函数注入就注入,别混进运行时状态
type WorkflowContext struct {
ReqID string
UserID int64
StartTime time.Time
DB *sql.DB // 注入,非初始化时硬写
}
<p>func stepValidate(ctx *WorkflowContext) error {
if ctx.UserID <= 0 {
return errors.New("invalid user ID")
}
return nil
}
错误处理必须中断后续步骤,但要保留已执行步骤的副作用
工作流不是事务——Go 没有内置回滚机制。你得自己决定哪些步骤可逆、哪些不可逆(比如发了短信就不能撤回),然后显式编码补偿逻辑。
立即学习“go语言免费学习笔记(深入)”;
- 用
for遍历步骤切片,遇到err != nil立即break,不要用defer堆叠 recover(掩盖真实错误) - 对于已成功执行但后续失败的步骤,补偿动作要单独定义,比如
stepSendSMS成功后,stepCharge失败,则调用stepRefundSMS - 别在步骤里直接 panic;panic 应只用于程序级异常(如空指针),业务错误一律用
error返回
测试工作流要测分支路径,而不是只跑 happy path
很多 Go 工作流代码上线后第一次遇到超时或网络错误就挂,因为单元测试只验证了 nil 错误路径。
- 用接口隔离外部依赖:把
HTTPClient、DBExecutor抽成 interface,测试时注入 mock 实现 - 为每个步骤单独写测试,再写组合测试;组合测试至少覆盖:正常链路、某步骤返回
errors.New("timeout")、某步骤返回context.DeadlineExceeded - 注意
time.Sleep在测试里是毒药;用clock.WithMockedTime或传递time.Now函数变量来控制时间敏感逻辑
复杂点在于状态一致性——比如步骤 A 写了 DB,步骤 B 发了消息,步骤 C 失败了,你怎么知道 A 的数据要不要清理?这没有银弹,只能按业务语义逐个约定。


















