轻量级工作流模块核心是统一Node接口(含context.Context和结构化错误)、用map+slice构建DAG依赖、拓扑排序防环、sync.WaitGroup+超时控制并发执行,50行内可落地。

直接上手写一个可用的轻量级工作流模块,不需要引入完整引擎,用原生 Go 就能搞定——关键是把 Node、Flow、Execute 三个概念落地,再加一层简单的 DAG 执行调度,50 行核心代码足够跑通真实场景。
定义 Node 接口和基础执行单元
所有任务节点必须统一行为契约,否则后续调度无法抽象。Go 的接口天然适合这事,但容易忽略两点:上下文传递和错误分类。
-
Execute方法签名里一定要带context.Context,否则超时控制、取消传播全得重写 - 别用
error返回泛化失败,建议封装成带状态码的结构体(比如ErrCodeRetryable),方便后续重试策略区分网络抖动和业务校验失败 - 每个
Node最好自带ID和Name字段,日志追踪和 UI 展示都依赖这个,别等出问题了再补
用 map + slice 构建 DAG 而不是手动连指针
新手常试图用链表或嵌套 struct 表达依赖,结果一加分支就崩。DAG 的本质是“谁等谁”,用两个数据结构就能稳住:
- 用
map[string]*Node存所有节点,string是节点 ID,查得快也避免循环引用 - 每个
Node加个DependsOn []string字段,存它依赖的上游节点 ID 列表 - 执行前调用拓扑排序(
topoSort函数),检测环路并返回错误 —— 这步不能跳,否则流程卡死没提示
并发执行时 goroutine 泄漏比性能更致命
很多人一上来就 go node.Execute(...),结果节点失败后 goroutine 挂在 channel 上不退出。正确做法是:
立即学习“go语言免费学习笔记(深入)”;
- 用
sync.WaitGroup控制生命周期,defer wg.Done()放在最外层 - 所有节点内部的 channel 操作必须配超时(
select+time.After),尤其是调外部 API 或 DB 查询 - 别共用一个
context.Background(),主流程传入的ctx要用context.WithTimeout或WithCancel包一层再下传
状态持久化先用内存 map,但留好扩展位
开发阶段硬上数据库或 Redis 反而拖慢迭代。用 map[string]ProcessInstance 完全够用,但要注意:
-
ProcessInstance结构体里必须有UpdatedAt time.Time,否则你没法判断某个实例是不是卡死了 - 所有写操作包一层
sync.RWMutex,读多写少场景下RLock比全锁高效得多 - 在初始化函数里埋个钩子,比如
if os.Getenv("PERSISTENCE") == "redis" { useRedisStore() },上线前一键切换
真正难的从来不是写完第一个版本,而是当第 3 个业务方要求加“人工审批节点”、第 5 个要“失败自动降级到邮件通知”时,你的 Node 接口还能不能不改就接住——这取决于一开始有没有把执行逻辑和控制逻辑彻底分开。


















