订单系统的核心,是状态管理。
一个订单从生到死,流经几十个状态。管不好,库存、财务都会乱套。
要理顺这一切,就需要:状态机。
状态机是什么
理解状态机,可以当成一张地铁路线图,标出了从起点到终点的所有站点和换乘路线。由四个元素构成:
-
状态(State):地图上的一个个站点,比如待支付、待发货。 -
事件(Event):促使你从一个站点到另一个站点的动作,比如成功付款这个动作让你从待支付站到了待发货站。 -
迁移(Transition):连接两个站点的线路,代表一次具体的状态变化。 -
动作(Action):当状态发生改变(像到站或换乘)时,需要做的具体事项。以订单为例,进入 待发货状态时,就进行通知仓库打包。
订单履约的状态
了解了状态机,咱们就来看看订单履约的路线图。首先是主干线,也就是订单的理想流程:
待支付--(支付成功)--> 待发货--(仓库发货)--> 待收货--(确认收货)--> 已完成
线路看起来简单清晰,但实际情况会复杂得多,需要把各种换乘和支线情况都考虑进来。
-
在 待支付站,乘客可能不想坐了(用户取消),或错过了(支付超时),需要一条通往已关闭站的支线。 -
在 待发货站,乘客可能突然反悔(申请退款),要引入退款中这类的临时换乘站。 -
在 已完成站,旅程看着像结束了,但乘客可能发现行李有问题(申请售后),要开启另一条名为售后的新线路。
把所有可能性都画上去,就得到了一张完整的订单履约全景图。
状态机的实现
地图画好了,作为工程师,我们如何用代码把它构建出来?这是一段有趣的探索之旅。
最简单的实现:if-else
第一反应,可能就是用 if-else或 switch来实现。
public void process(Order order, Event event) {
if (order.getStatus() == OrderStatus.PENDING_PAYMENT) {
if (event == Event.PAY_SUCCESS) {
order.setStatus(OrderStatus.PENDING_SHIPMENT);
// ...其他业务逻辑
} else if (event == Event.CANCEL) {
order.setStatus(OrderStatus.CLOSED);
}
} else if (order.getStatus() == OrderStatus.PENDING_SHIPMENT) {
// ...
}
}
在只有两三个站点的简单线路上,这很管用。但当地图变得复杂,if-else就会迅速变成一团乱麻。每当产品经理想增加一个新站点或新线路,我们都得硬着头皮去修改这个庞大而脆弱的结构,一不小心就会引发全线交通瘫痪。
进阶之路:状态模式
if-else显然是条死胡同。我们应该把每个站点的逻辑独立开来。于是,状态模式(State Pattern)进入了我们的视野。
// 状态接口
publicinterface OrderState {
void handle(Order context, Event event);
}
// 具体状态类:待支付
publicclass PendingPaymentState implements OrderState {
@Override
public void handle(Order context, Event event) {
if (event == Event.PAY_SUCCESS) {
context.setState(new PendingShipmentState());
// ...业务动作
}
}
}
每个状态的职责变得清晰,添加新状态也更容易了。但新的问题随之而来:当地图上有几十个站点时,我们就要维护几十个 State类。系统的复杂性从一个巨大的 if-else转移到了数量庞大的类上。
表驱动引擎
这张地铁图的逻辑,能否不写死在代码里?如果它本身就是一份配置,那该多好?
这就是表驱动思想。把状态流转的所有规则,定义在一张配置表里。
这张表就是我们的线路图数据。然后,a只需要一个通用的列车调度中心(状态机引擎)来读取这张图。
// 伪代码:状态机引擎
publicclass StateMachineEngine {
// 加载上面的状态驱动表
private Map<CurrentState, Map<Event, Transition>> transitions;
public void fire(StatefulObject obj, Event event) {
// 根据当前状态和事件,查表找到对应的线路
Transition transition = findTransition(obj.getCurrentState(), event);
if (transition != null) {
// 执行动作
transition.getAction().execute();
// 驶向下一站
obj.setCurrentState(transition.getNextState());
} else {
// 没有这条线路,报告异常
thrownew IllegalStateException("Invalid transition.");
}
}
}
这才是我们想要的优雅!现在,无论是修改线路,还是增加站点,都只是修改配置数据而已,核心的调度中心代码岿然不动。系统获得了前所未有的灵活性和可维护性。
开源方案
自己动手构建一个简单的引擎很有趣,但在真实的生产环境中,我们完全可以站在巨人的肩膀上。
业界有许多成熟的开源状态机框架,比如 Spring Statemachine、Squirrel-foundation 等。它们就是我们设想的那个列车调度中心的专业、豪华版,提供了更完善的功能:
-
声明式配置:用更简洁的方式定义线路图。 -
持久化支持:能记住每列火车当前所在的站点,即使系统重启也不会丢失。 -
监听与拦截:方便地在列车进出站的各个节点挂载额外任务。 -
分布式支持:在庞大的铁路网中确保状态的准确无误。
使用 Spring Statemachine,定义上面那张图的核心逻辑,代码会非常清晰:
@Configuration
@EnableStateMachine
publicclass OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStatus, OrderEvent> {
@Override
public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states) throws Exception {
states.withStates()
.initial(OrderStatus.PENDING_PAYMENT) // 定义起点站
.states(EnumSet.allOf(OrderStatus.class)); // 定义所有站点
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions) throws Exception {
transitions
.withExternal() // 定义一条线路
.source(OrderStatus.PENDING_PAYMENT).target(OrderStatus.PENDING_SHIPMENT) // 从..到..
.event(OrderEvent.PAY_SUCCESS) // 需要支付成功这张票
.and()
.withExternal() // 再定义一条
.source(OrderStatus.PENDING_SHIPMENT).target(OrderStatus.TO_BE_RECEIVED)
.event(OrderEvent.SHIP);
}
}
写在最后
从 if-else到表驱动引擎的演进,本质是思维方式的转变:把流程当数据来管理。
状态机就是这个思想的载体。它是一种将复杂抽象为简单、将混乱转化为秩序的工具。
手握此图,万变不惊。
▲ 添加微信:JiteStart,获取架构资料
往期推荐
AI提效
架构设计

