必须建模为有向无环图(DAG)并分粒度持久化状态,否则审批、分支、并行等复杂流程将不可维护;需拓扑排序防环、实例ID全局唯一、状态分层存Redis+PostgreSQL、任务用asynq调度并保障事务一致性。

直接说结论:别用嵌套 if-else 或裸 goroutine 拼流程,必须建模为有向无环图(DAG),并分粒度持久化状态。
用 DAG 替代 if-else 控制流
业务流程一旦涉及审批、分支、并行、汇合、重试,靠函数调用链或条件判断就不可维护。比如加一个“法务复核”节点,就得翻五层函数改逻辑,还容易漏掉错误回滚路径。
- 每个节点定义为统一签名的函数:
func(ctx context.Context, data map[string]interface{}) (map[string]interface{}, error) - 依赖关系用邻接表表达:
map[string][]string{"submit": ["audit", "risk_check"], "audit": ["notify"]} - 启动前必须做拓扑排序检测环路——哪怕只有一处
A → B → A,运行时就会卡死 - 别手写 DFS 验证环;用
github.com/yourbasic/graph或封装好的dag.TopologicalSort工具
状态必须分层持久化,不能只靠内存变量
服务重启、Pod 重建、并发请求一上来,全靠 map[string]interface{} 存上下文,等于把状态存在沙上。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 每个流程实例必须有唯一
instanceID,所有状态变更都以它为 key 写入存储 - 高频字段(当前节点名、重试次数、最后更新时间)存 Redis,设
TTL避免堆积 - 关键字段(原始表单、审批意见、支付凭证)走 PostgreSQL,在
workflow_instances表里按instanceID分区 - 别每步全量序列化整个
data;用jsonpatch记 diff,或自定义 dirty 标记字段减少 IO - 状态更新和业务操作(如扣库存)必须在同一个 DB 事务中提交,否则出现“流程已进下一节点,但订单未扣款”这种错位
选 asynq 而不是自研调度器,除非你真得绕过 Redis
纯 channel + worker pool 只能跑单任务,撑不起带依赖、超时、重试、结果传递的业务流程。
-
asynq不是重型引擎,但用 Redis 做后端,天然支持失败重入、延迟触发、手动重试、HTTP API 查看状态 - 链式调用靠在 handler 里调用
client.Enqueue实现,不是go next()—— 后者无法保证顺序、失败不重试、结果不传递 - 每个任务类型注册独立 handler:
mux.HandleFunc("approve_order", approveHandler),便于灰度和监控 - 若因合规要求不能用 Redis,自研必须补足四点:DAG 建模、实例 ID 全局唯一、显式控制流原语(
WaitForAll/Switch)、取消传播(context.CancelFunc要能中断已排队未执行的任务)
真正难的不是启动流程,而是让流程在失败、重启、扩缩容后还能续跑——这取决于你是否从第一天就区分了“瞬态上下文”和“持久快照”,而不是等线上出三次数据错位再回头补状态表。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










