状态机建模
用途:单据/工单类业务反复出现“非法状态变更”缺陷时查阅
定义
「定义」小节链接状态机用有限的状态集合、事件(或命令)集合与迁移函数描述对象生命周期:当前状态 + 事件 → 下一状态(含守卫条件与副作用)。
在业务系统里,它通常是聚合根的核心资产:单据的复杂度大多不在计算,而在“什么状态下允许什么操作”。
由来与要解决的问题
「由来与要解决的问题」小节链接不用显式状态机时的典型形态:多个布尔字段加状态码组合判断(if (status == 3 && !deleted && approved))。三个缺陷:非法组合无法在编译期排除;迁移规则散落在各处,新增状态要全局搜索;审计与回放困难。
显式状态机把迁移合法性集中到一处,使“非法迁移”成为不可能事件而不是运行时意外。
建模步骤
「建模步骤」小节链接- 枚举状态:只列业务上真实存在的状态,避免“临时状态”;区分主状态与子状态(如“已关闭”下的“正常关闭/超时关闭”)。
- 枚举事件:来自用户操作、定时任务、外部回执的事件分开标注。
- 写迁移表:
(源状态, 事件, 守卫) → (目标状态, 动作)。表中未列出的组合即非法。 - 定义副作用:进入/退出状态时产出的领域事件、通知、补偿动作。
- 落库与审计:状态字段 + 迁移历史(谁、何时、因何事件),支持追溯。
- 并发保护:迁移方法在聚合根上,配合版本号做乐观锁,防止并发迁移产生跳跃。
实现形态
「实现形态」小节链接| 形态 | 适用 | 代价 |
|---|---|---|
| 枚举 + 迁移表(代码内 Map/switch) | 状态数 < 20、迁移稳定 | 图与代码需人工同步 |
| 状态机框架 | 迁移复杂、需要监听器与守卫 | 学习成本、调试间接层 |
| 工作流/审批引擎 | 需要“谁在什么时候处理”的人工任务编排 | 引擎不承担业务规则,误用会把规则塞进流程配置 |
| 事件溯源 | 需要完整历史与时间旅行 | 高复杂度,只在有强审计需求时选 |
关键分工:审批流引擎管“流转与待办”,状态机管“业务状态是否允许该变更”。让流程引擎直接改业务状态字段,是规则失控的常见起点。
开源参考:Spring StateMachine、Koloda(轻量 Java 状态机)、AWS Step Functions / Temporal(长流程编排,属于工作流而非实体状态机)。
常见误区
「常见误区」小节链接| 误区 | 修正 |
|---|---|
| 用布尔字段拼状态 | 单一状态枚举 + 迁移表 |
状态定义包含实现细节(sync_flag=2) |
状态是业务概念,不是同步进度 |
| 迁移规则散在 Controller/Job/MQ 消费者 | 收敛到聚合根方法,入口只调用 |
| 无迁移历史 | 记录状态变更事件,支撑审计与排障 |
| 并发下双写迁移 | 乐观锁 + 迁移前重新校验守卫 |
| 把流程引擎当状态机 | 两者职责不同,需明确谁拥有业务状态 |
与其他概念的关系
「与其他概念的关系」小节链接- 聚合根:迁移合法性由根守护,状态是值对象(枚举)。
- 领域事件:每次成功迁移产出一个事件,供下游订阅与审计。
- 规范模式:守卫条件适合表达为 Specification,便于组合与测试。
- 分布式事务:跨系统状态推进常需要 Saga 或最大努力通知兜底。
延伸阅读
「延伸阅读」小节链接- Martin Fowler《企业应用架构模式》(State Strategy 章节)
- Vaughn Vernon《实现领域驱动设计》(聚合与状态迁移)
- Robert Martin《Clean Architecture》(策略与状态解耦的一般原则)