从零构建生产级状态机引擎

从零构建生产级状态机引擎

从零构建生产级状态机引擎:深度解析状态模式、Saga 事务与并发控制

适用读者:3年以上 Java 开发经验,熟悉 Spring Boot,对分布式系统设计有兴趣


一、那个没人敢动的 300 行 switch-case​​

凌晨两点,生产告警:订单支付成功但状态卡在"待支付"。你翻开 OrderService.java​,看到一段横跨 300 行的 switch-case​,每个 case​ 里有 3 层 if-else​ 嵌套------加库存校验、加风控拦截、加对账补偿,三年里 N个开发往上堆过代码,注释里的 // FIXME: 这里可能会重复扣款​ 已经挂了 11 个月。

java 复制代码
if (order.getStatus() == INIT) {
    if (event == PAY) {
        order.setStatus(PAID);
        deductBalance();
    }
} else if (order.getStatus() == PAID) {
    if (event == SHIP) {
        order.setStatus(SHIPPED);
        createShipment();
    }
}

看到了吗?每次加一个状态,你要修改 N 个已有的 **case**​​ ;每次加一个事件,你要在 N 个 **case**​​ 里塞新分支。复杂度是 O(N × M),不出三个月就会达到人类心智的极限。

状态机的本质不是写一段逻辑,而是声明一张有向图。节点是状态(State),边是事件驱动的迁移(Transition)。每条边上挂载着守卫条件(Guard,决定能不能走)、业务动作(Action,决定走的时候干什么)、失败落点(failState)和自动触发(autoEvent)。当你用图的思维去建模业务流转,问题就从"我要写多少行 if-else"变成了"我要画一张什么样的图"。一张图拿给产品经理看,他能确认业务规则对不对;拿给测试看,他能穷举所有路径写 case;拿给新同事看,他十分钟就能上手------而不是对着 300 行 switch-case 怀疑人生。

本文将基于一个完全从零构建、不依赖任何状态机框架的 Java 引擎,层层深入以下硬核话题:图遍历算法如何替代流程编排引擎,让一次 fireEvent​​ 自动走到终态;Saga 补偿为什么不能只靠逆序回调,双重补偿策略的适用边界在哪里;分布式锁 + CAS + 幂等为什么缺一不可,三层防御体系如何设计;先持久化状态再执行业务动作,是反直觉还是不得不------一个关键执行顺序的完整推导。读完你会发现:一个好的状态机引擎,不光是消灭 if-else​​,更是把隐式的"程序逻辑"变成显式的"业务资产"。


二、核心模型:状态机就是一张有向图

2.1 数学本质

一个确定有限状态机 (DFA) 由五元组定义:

M = (S, E, δ, s₀, F)

  • S:有限状态集合
  • E:有限事件集合
  • δ: S × E → S:状态转移函数
  • s₀:初始状态
  • F:终结状态集合

但在业务系统里,纯 DFA 不够用。我们需要:

  • 守卫条件 (Guard) :δ 的执行不光是查表,还要满足前置条件
  • 副作用动作 (Action) :状态迁移时要执行真正的业务逻辑
  • 失败处理 (Failure) :动作失败后怎么办?

于是我们扩展模型:

Transition = (S_from, S_to, E, Guards, Actions, S_fail, E_auto, E_failAuto)

2.2 代码中的建模

核心转移类 StateTransition​ 的设计:

java 复制代码
public class StateTransition<S, E> {
    private S fromState;          // 源状态
    private S toState;            // 目标状态
    private E event;              // 触发事件
    private List<IStateMachineAction> actions;   // 业务动作
    private List<IStateMachineGuard> guards;     // 前置守卫
    private S failState;          // 失败落点状态(可选)
    private E autoEvent;          // 成功后自动触发的事件(可选)
    private E failAutoEvent;      // 失败后自动触发的事件(可选)
}

这里最精妙的设计是 autoEvent​ 和 failAutoEvent​。它们让状态机具备了"自动推进"的能力------一次 fireEvent​ 调用可以沿着图的深度一路走到终结状态。后文会详细展开。


三、引擎架构:分层解耦的六大抽象

整个引擎围绕六个核心接口构建,每个都代表了一个可替换的横切关注点:

scss 复制代码
┌──────────────────────────────────────────────────────┐
│                  StateMachineEngine                   │
│          (核心调度器,编排所有组件协作)                   │
├──────────────────────────────────────────────────────┤
│  ┌──────────┐ ┌──────────┐ ┌───────────────────┐    │
│  │  Guard   │ │  Action  │ │    Listener        │    │
│  │ (前置条件) │ │ (业务动作) │ │   (生命周期钩子)     │    │
│  └──────────┘ └──────────┘ └───────────────────┘    │
│  ┌──────────┐ ┌──────────┐ ┌───────────────────┐    │
│  │Persister │ │ Metrics  │ │  ConfigBuilder    │    │
│  │(持久化锁) │ │ (可观测性) │ │   (图定义)         │    │
│  └──────────┘ └──────────┘ └───────────────────┘    │
└──────────────────────────────────────────────────────┘

3.1 为什么每个横切关注点都要拆成接口?

这不是过度设计。我们逐一分析接口背后的设计意图:

​**IStateMachinePersister**​ --- 持久化与并发控制的一体化抽象

java 复制代码
public interface IStateMachinePersister<S, E, C> {
    S loadState(C context);
    StateMachineResult persistState(C context, S from, S to, E event);
    StateMachineResult rollbackState(C context, S from, S to, E event);
    boolean isTransitionExecuted(C context, E event);
    boolean tryLock(C context, E event);
    void unlock(C context, E event);
}

注意:持久化和分布式锁是同一个接口 。这不是随意合并------在分布式环境下,状态存储和锁存储往往是同一个基础设施(Redis 的 SETNX​ 既做锁又存状态;MongoDB 的 findAndModify​ 既是 CAS 写入也是原子操作)。把锁和持久化放在同一个抽象里,让实现者可以基于同一个数据源做原子性保证,避免分布式事务。

​**ICompensableAction**​ --- 嵌入 Saga 模式的动作

java 复制代码
public interface ICompensableAction<S, E, C> extends IStateMachineAction<S, E, C> {
    void compensate(C context, E event, S fromState, S toState);
}

常规 Action 只定义正向操作。ICompensableAction​ 额外定义 compensate()​ 逆向操作。当一个长事务中后面的步骤失败时,引擎按 LIFO 顺序回调之前所有成功步骤的 compensate()​------这是 Saga 模式的经典实现。补偿数据通过 context​ 中的 compensationData​ 传递。

3.2 引擎调度器:fireEvent​ 的完整生命周期

这是整个引擎最核心的方法。我将其执行路径完整呈现:

scss 复制代码
fireEvent(context, event)
│
├─ [0] tryLock(context, event) → 获取分布式锁
│      └─ 失败 → 返回 concurrentConflict (不阻塞,快速失败)
│
├─ [1] isTransitionExecuted(context, event) → 幂等性检查
│      └─ 已执行 → 直接返回 success
│
├─ [2] loadState(context) → 加载当前状态
│
├─ [3] config.getTransition(fromState, event) → 查表
│      └─ 无匹配 → 返回 invalidTransition
│
├─ [4] onTransitionStart() → 通知监听器
│
├─ [5] guards.evaluate() → 逐个评估守卫
│      └─ 任一为 false → onGuardRejected()
│                      → handleFailure() (可能进入 failState)
│                      → onTransitionFailure()
│                      → 返回 guardRejected
│
├─ [6] persistState(from, to, event) → CAS 写入新状态
│      └─ 失败 (并发冲突) → 返回 concurrentConflict
│
├─ [7] actions.execute() → 逐个执行动作
│      └─ 某步失败 →
│          ├─ 有 failState → handleFailure() → 进入 failState
│          └─ 无 failState → compensateActions() → Saga 补偿
│          → onTransitionFailure() → 返回 failure
│
├─ [8] onTransitionSuccess() → 通知监听器
│
├─ [9] autoEvent != null → 递归 fireEvent(context, autoEvent)
│      └─ 形成链式自动推进
│
└─ [finally] unlock(context, event) → 释放分布式锁

为什么 persistState 在 actions 之前执行?

这是一个精妙的设计决策。传统的 Saga 实现是先执行正向操作再持久化状态,但这里的顺序是反的:先持久化状态,再执行业务逻辑。原因有三:

  1. 可见性:状态已经写入存储,任何查询方都能看到"正在处理中"的状态,而不是旧状态
  2. 故障恢复 :如果 actions 执行到一半进程崩溃,重启后加载到的状态是 toState,可以根据这个状态做恢复判断
  3. 与 compensation 的配合:动作失败时,补偿策略根据状态已经写入这一事实来决策------如果是 CAS 失败回滚状态,如果是业务失败则进入 failState

当然,这也意味着 actions 必须是幂等的 或者可补偿的 ------这正是 ICompensableAction​ 存在的理由。


四、深度机制一:自动化链式推进

这是本引擎最与众不同的特性。先看订单状态机的实际拓扑:

scss 复制代码
INIT ──CHECK_STOCK──▶ STOCK_CHECKED ──CHECK_BALANCE──▶ BALANCE_CHECKED
  │                      │                                    │
  │                      │ fail→STOCK_INSUFFICIENT             │ fail→COMPENSATING
  │                      │                                    │
  │                      ▼ autoEvent                          ▼ autoEvent
  │               CHECK_BALANCE                         DEDUCT_BALANCE
  │                      │                                    │
  ▼                      ▼                                    ▼
CANCEL              (自动触发)                            (自动触发)
                    
BALANCE_CHECKED ──DEDUCT_BALANCE──▶ PAID ──SHIP──▶ SHIPPED ──CONFIRM──▶ COMPLETED
                                         │             
                                         │ fail→COMPENSATING
                                         │ autoEvent→SHIP
                                         ▼
                                    (自动触发)

一次调用,自动走完全程

java 复制代码
// 用户下单,触发 CHECK_STOCK 事件
StateMachineResult result = engine.fireEvent(context, OrderEvent.CHECK_STOCK);

// 实际执行路径(自动链式推进):
// INIT → STOCK_CHECKED → BALANCE_CHECKED → PAID → SHIPPED → COMPLETED

秘密在于每个 Transition 上的 autoEvent​:

java 复制代码
StateTransition.<OrderStatus, OrderEvent>builder()
    .from(INIT).to(STOCK_CHECKED)
    .event(CHECK_STOCK)
    .guard(stockGuard)
    .action(deductStockAction)
    .autoEvent(CHECK_BALANCE)      // ← 成功后自动触发下一个事件
    .failState(STOCK_INSUFFICIENT)  // ← 失败时进入终态
    .build();

fireEvent​ 在第 9 步检测到 autoEvent != null​,递归调用自身,形成深度优先遍历:

java 复制代码
// 引擎中的实际代码
if (transition.hasAutoEvent()) {
    return fireEvent(context, transition.getAutoEvent());
}

这本质上是把"流程编排"的能力下沉到了"状态转移"这个粒度。开发者不需要写一个外部的流程引擎(如 Camunda)来串联步骤,而是在定义单个转移时就声明了"接下来做什么"。

失败路径同样自动推进

当库存不足时,failState(STOCK_INSUFFICIENT)​ + failAutoEvent​ 可以让引擎自动进入补偿链路:

java 复制代码
.builder()
    .from(STOCK_CHECKED).to(BALANCE_CHECKED)
    .failState(COMPENSATING)                          // 失败落点
    .failAutoEvent(COMPENSATE_RESTORE_STOCK)           // 自动触发补偿
    .build();

成功路径和失败路径都在一张图里声明式定义,引擎负责自动遍历。


五、深度机制二:双重补偿策略

本引擎实现了两种补偿模式,适用于不同的业务场景。

策略 A:Saga 自动补偿(无 failState)

当 Transition 没有配置 failState​ 时,动作执行失败会触发 Saga 补偿:

java 复制代码
// 引擎中的补偿逻辑
private void compensateActions(C context, E event, S fromState, S toState,
                                List<IStateMachineAction> actions) {
    // LIFO 逆序补偿
    for (int i = actions.size() - 1; i >= 0; i--) {
        IStateMachineAction action = actions.get(i);
        if (action instanceof ICompensableAction) {
            ((ICompensableAction) action).compensate(context, event, fromState, toState);
        }
    }
}

适用场景:无状态的微服务调用、不需要审计追踪的内部操作。比如支付完成后发货失败,自动退款------用户无感知、无需人工介入。

策略 B:显式 failState 补偿(有 failState)

当 Transition 配置了 failState​ 时,失败不会触发 Saga 补偿,而是将状态写入 failState​ 并触发 failAutoEvent​:

java 复制代码
// 还款失败 → 进入 COMPENSATING 状态 → 触发 COMPENSATE_RESTORE_STOCK
// COMPENSATING 状态下:
//   COMPENSATE_RESTORE_STOCK → RestoreStockCompensationAction → autoEvent: COMPENSATE_FINISH
//   COMPENSATE_FINISH → CompensationFinishAction → 进入 BALANCE_INSUFFICIENT (终态)

适用场景:需要人工介入、需要审计日志、或者补偿本身也可能失败的长事务。每个补偿步骤都是显式的状态迁移,可以被监控、告警、重试。

两种策略的选择矩阵

维度 Saga 自动补偿 显式 failState 补偿
可见性 低(隐式回调) 高(显式状态)
可重试性 依赖引擎重试 可独立重试每个补偿步骤
可观测性 需额外埋点 状态变更即埋点
复杂度 中等(需设计补偿状态机)
适用场景 无副作用操作 有副作用的业务操作

六、深度机制三:并发控制的三重保障

状态机在分布式环境下的并发安全是整个系统正确性的基石。本引擎实现了三层防护:

第一层:分布式锁(入口防护)

java 复制代码
// 引擎入口
if (!persister.tryLock(context, event)) {
    return StateMachineResult.concurrentConflict("锁获取失败");
}
try {
    return doFireEvent(context, event);
} finally {
    persister.unlock(context, event);
}

注意这里用的是 tryLock()​ 而非 lock()​------快速失败而非阻塞等待。原因:状态机转移通常很快(毫秒级),如果锁被持有,说明有并发操作正在进行,与其阻塞等待不如让调用方重试。这避免了锁竞争导致的线程堆积。

生产实践 :Redis SET key value NX EX 10​ 实现。key 粒度是 entityId:event​。

第二层:CAS 乐观锁(写入防护)

即使拿到了分布式锁,persistState​ 仍然要做 CAS 校验:

java 复制代码
// InMemoryOrderPersister 中的 CAS 实现
public StateMachineResult persistState(OrderContext context, OrderStatus from, 
                                        OrderStatus to, OrderEvent event) {
    String orderId = context.getOrder().getOrderId();
    OrderStatus current = stateStore.get(orderId);
  
    // 校验:当前状态必须是期望的 fromState
    if (current != null && current != fromState) {
        return StateMachineResult.concurrentConflict(
            String.format("状态冲突: 期望=%s, 实际=%s", from, current));
    }
  
    stateStore.put(orderId, toState);
    recordEvent(orderId, event);  // 同时写入幂等日志
    return StateMachineResult.success();
}

为什么有了锁还要 CAS? 分布式锁有超时机制。如果锁在 10 秒后自动过期,而前一个操作仍然在执行(网络延迟、GC 停顿),就会有两个操作同时持有锁。CAS 是最后一道防线------即使锁失效了,CAS 也能检测到状态已经被修改。

第三层:幂等性保障(重放防护)

java 复制代码
if (persister.isTransitionExecuted(context, event)) {
    // 该事件已经处理过,直接返回成功
    return StateMachineResult.success();
}

isTransitionExecuted​ 检查事件日志:每个 (entityId, event)​ 组合只能成功执行一次。这在消息队列的 at-least-once 投递语义下至关重要。

实现细节:状态写入和事件日志写入必须在同一个原子操作中完成(Redis 用 Lua 脚本,MongoDB 用事务或嵌入式文档),否则会出现"状态变了但没记录"或"记录了但状态没变"的不一致。


七、设计模式全景:不止于 GoF

几种教科书模式的实战组合:

Builder 模式的正确用法:处理可选参数爆炸

StateTransition​ 有 8 个字段,其中 5 个是可选的。如果全部通过构造函数传递,组合爆炸不可维护。Builder 模式让每个可选字段独立设置:

java 复制代码
StateTransition.<OrderStatus, OrderEvent>builder()
    .from(INIT)
    .to(STOCK_CHECKED)
    .event(CHECK_STOCK)
    .guard(stockGuard)            // 可选
    .action(deductStockAction)    // 可选
    .autoEvent(CHECK_BALANCE)     // 可选
    .failState(STOCK_INSUFFICIENT) // 可选
    .failAutoEvent(null)          // 可选
    .build();

Template Method 模式:约束配置流程

IStateMachineConfigBuilder​ 的 build()​ 方法是模板方法:

java 复制代码
default StateMachineConfig<S, E, C> build(String machineType) {
    StateMachineConfig<S, E, C> config = new StateMachineConfig<>(machineType);
    configureTransitions(config);   // 子类实现:定义转移图
    configureListeners(config);     // 子类可选:注册监听器
    config.setInitialState(getInitialState());    // 子类实现
    config.setTerminalStates(getTerminalStates());// 子类实现
    return config;
}

子类只需要填空,不需要理解构建流程。这保证了所有状态机配置的一致性和完整性。

Double-Checked Locking 的正确实现

StateMachineFactory​ 需要保证全局 JVM 范围内同类型状态机的配置只构建一次:

java 复制代码
// 类级别缓存(跨实例共享)
private static final ConcurrentHashMap<String, Object> GLOBAL_CONFIG_CACHE = new ConcurrentHashMap<>();

// 实例级别缓存
private volatile StateMachineConfig<S, E, C> cachedConfig;

private StateMachineConfig<S, E, C> getConfig() {
    if (cachedConfig != null) return cachedConfig;           // ① 快速路径
    synchronized (this) {
        if (cachedConfig != null) return cachedConfig;       // ② 双重检查
        // ③ 先查全局缓存
        StateMachineConfig global = GLOBAL_CONFIG_CACHE.get(machineType);
        if (global != null) { cachedConfig = global; return cachedConfig; }
        // ④ 构建并缓存
        StateMachineConfig config = configBuilder.build(machineType);
        GLOBAL_CONFIG_CACHE.put(machineType, config);
        cachedConfig = config;
        return cachedConfig;
    }
}

这里两层缓存的设计值得注意:实例级别用 volatile​ 保证可见性,全局级别用 ConcurrentHashMap​ 保证跨实例共享。这是典型的"先查本地、再查共享、最后构建"三级缓存模式。


八、可观测性:让状态机不再是黑盒

IStateMachineListener​ 提供了四个生命周期钩子:

java 复制代码
public interface IStateMachineListener<S, E, C> {
    default void onTransitionStart(S from, S to, E event, C context) {}
    default void onTransitionSuccess(S from, S to, E event, C context) {}
    default void onTransitionFailure(S from, S to, E event, C context, StateMachineResult result) {}
    default void onGuardRejected(S from, S to, E event, C context, IStateMachineGuard guard) {}
}

IStateMachineMetrics​ 提供了监控埋点:

java 复制代码
public interface IStateMachineMetrics {
    void recordTransition(S from, S to, E event, boolean success, long durationMs);
    void recordGuardRejected(S from, S to, E event, String guardName);
    void recordCompensation(S from, S to, E event, boolean success, long durationMs);
}

生产实践:在 Metrics 实现中对接 Micrometer:

java 复制代码
// 记录每次状态迁移的耗时分布
Timer.builder("statemachine.transition.duration")
    .tag("machineType", machineType)
    .tag("from", from.toString())
    .tag("to", to.toString())
    .tag("status", success ? "success" : "failure")
    .register(meterRegistry)
    .record(durationMs, TimeUnit.MILLISECONDS);

配合 Grafana 面板,你可以看到:

  • 哪个状态迁移最频繁(系统热点)
  • 哪个 Guard 拦截率最高(业务瓶颈)
  • 补偿触发频率(系统健康指标)

九、生产级改造:从 Demo 到线上

当前项目是教学级实现。要将它推向生产环境,需要在以下维度加固:

9.1 持久化:从 ConcurrentHashMap 到 MongoDB

java 复制代码
// 生产级 persister 的 MongoDB 实现思路
public StateMachineResult persistState(C context, S from, S to, E event) {
    String entityId = extractId(context);
  
    // findAndModify 是原子操作,天然支持 CAS
    Document query = new Document("_id", entityId)
        .append("state", from.toString());  // CAS 条件
    Document update = new Document("$set", 
        new Document("state", to.toString()))
        .append("$push", 
        new Document("eventLog", event.toString()));  // 同时写入事件日志
  
    Document result = collection.findOneAndUpdate(query, update, 
        new FindOneAndUpdateOptions().returnDocument(ReturnDocument.BEFORE));
  
    if (result == null) {
        return StateMachineResult.concurrentConflict("状态已被其他操作修改");
    }
    return StateMachineResult.success();
}

9.2 分布式锁:从 ReentrantLock 到 Redis

java 复制代码
public boolean tryLock(C context, E event) {
    String lockKey = "lock:" + extractId(context) + ":" + event;
    // SET key value NX EX 10
    return redisTemplate.opsForValue()
        .setIfAbsent(lockKey, Thread.currentThread().getName(), 
                     Duration.ofSeconds(10));
}

9.3 事件溯源 (Event Sourcing)

如果未来需要完整的审计追踪,可以将每次状态变更作为事件存入事件流:

java 复制代码
// 在 persistState 成功后
eventStore.append(new StateChangedEvent(entityId, from, to, event, timestamp));

这样任何历史状态都可以通过重放事件重建。

9.4 测试覆盖

当前项目没有测试。生产级项目至少需要:

  • 单元测试:每个 Action、Guard 的独立逻辑
  • 状态机集成测试:每种转移路径的端到端验证
  • 并发测试 :多线程同时 fireEvent 验证锁和 CAS
  • 补偿测试:模拟各种失败场景验证补偿链

十、核心代码解析:fireEvent 完整实现

为了让读者彻底理解引擎的工作方式,我将核心方法完整展示:

java 复制代码
public StateMachineResult fireEvent(C context, E event) {
    // ===== 第〇步:分布式锁 =====
    if (!persister.tryLock(context, event)) {
        return StateMachineResult.concurrentConflict(
            "获取分布式锁失败,可能存在并发操作");
    }
  
    try {
        return doFireEvent(context, event);
    } finally {
        persister.unlock(context, event);
    }
}

private StateMachineResult doFireEvent(C context, E event) {
    // ===== 第一步:幂等性检查 =====
    if (persister.isTransitionExecuted(context, event)) {
        return StateMachineResult.success();
    }
  
    // ===== 第二步:加载当前状态 =====
    S currentState = persister.loadState(context);
  
    // ===== 第三步:查找转移配置 =====
    StateTransition<S, E> transition = config.getTransition(currentState, event);
    if (transition == null) {
        return StateMachineResult.invalidTransition(currentState, event);
    }
  
    // ===== 第四步:通知监听器 =====
    notifyTransitionStart(currentState, transition.getToState(), event, context);
  
    // ===== 第五步:守卫检查 =====
    for (IStateMachineGuard<S, E, C> guard : transition.getGuards()) {
        if (!guard.evaluate(context, event, currentState, transition.getToState())) {
            notifyGuardRejected(currentState, transition.getToState(), 
                               event, context, guard);
            handleFailure(context, event, currentState, transition);
            return StateMachineResult.guardRejected(guard.getName(), 
                       "守卫条件不满足: " + guard.getName());
        }
    }
  
    // ===== 第六步:持久化新状态(CAS)=====
    StateMachineResult persistResult = persister.persistState(
        context, currentState, transition.getToState(), event);
    if (!persistResult.isSuccess()) {
        return persistResult;  // 并发冲突
    }
  
    // ===== 第七步:执行业务动作 =====
    for (IStateMachineAction<S, E, C> action : transition.getActions()) {
        StateMachineResult actionResult = 
            action.execute(context, event, currentState, transition.getToState());
      
        if (!actionResult.isSuccess()) {
            // 动作失败 → 触发失败处理
            handleFailure(context, event, currentState, transition);
            notifyTransitionFailure(currentState, transition.getToState(), 
                                    event, context, actionResult);
            return actionResult;
        }
    }
  
    // ===== 第八步:成功通知 =====
    notifyTransitionSuccess(currentState, transition.getToState(), event, context);
  
    // ===== 第九步:自动链式触发 =====
    if (transition.hasAutoEvent()) {
        return fireEvent(context, transition.getAutoEvent());
    }
  
    return StateMachineResult.success();
}

十一、总结与思考

设计取舍

设计选择 优势 代价
先持久化再执行动作 状态即时可见;崩溃可恢复 动作必须是可补偿或幂等的
快速失败锁(tryLock) 避免线程堆积 调用方需实现重试
Java 代码定义状态图 类型安全、IDE 支持好 无法热更新(需重新部署)
无外部依赖(纯手写引擎) 零学习成本、完全可控 需要团队理解引擎内部实现

什么时候不该用状态机?

  1. 状态数 < 3 且未来不会增加:直接用 if-else 更简单
  2. 没有状态概念的业务:比如纯计算服务
  3. 流程高度非结构化:比如工单系统中有大量 ad-hoc 跳转,状态图会变成全连接图

下一步进阶方向

  1. DSL 层:在 Java Builder 之上再封装一层 YAML/JSON DSL,支持热更新
  2. 可视化编辑:基于状态机的图结构,可以自动生成 D3.js/Graphviz 可视化
  3. 持久化状态机实例:引擎本身也可序列化,实现"暂停-恢复"机制
  4. 分层状态机:支持 UML 中的分层状态概念(子状态机)
  5. 事件溯源完整实现:所有状态变更作为一等事件持久化,支持任意时间点回溯

代码仓库 :本文基于 order-statemachine​ 项目,完整源码涵盖引擎核心(6个抽象接口 + 3个核心类)和订单领域示例(16个类),总计约 1100 行 Java 代码,无外部状态机库依赖,纯 Spring Boot 2.7 + JDK 11 构建。

如果你正在设计或重构一个状态驱动的业务系统,希望本文为你提供了从原理到实践的完整参考。一个好的状态机引擎,不光是消灭 if-else​,更是让你的业务规则显式化、可测试、可观测、可扩展。