“这个审批流程又改了,要加两个新状态和三种流转条件...”
当我第N次听到产品经理这样说时,看着代码里层层嵌套的if-else,我知道是时候做出改变了。那个曾经“简单”的审批系统,已经变成了谁都不敢碰的 代码沼泽。
一、if-else地狱:我们曾经走过的弯路
1.1 典型的复杂业务逻辑
想象一下这样一个订单状态流转逻辑:
public class OrderService {
public void handleEvent(Order order, OrderEvent event) {
if (order.getStatus() == OrderStatus.WAITING_PAYMENT) {
if (event == OrderEvent.PAY_SUCCESS) {
if (order.getAmount() > 10000) {
// 大额订单需要风控审核
order.setStatus(OrderStatus.WAITING_RISK_REVIEW);
riskReviewService.submit(order);
} else {
order.setStatus(OrderStatus.PAID);
inventoryService.reduceStock(order);
}
} else if (event == OrderEvent.PAY_TIMEOUT) {
order.setStatus(OrderStatus.CLOSED);
inventoryService.restoreStock(order);
} else if (event == OrderEvent.USER_CANCEL) {
if (order.getCreateTime().isAfter(LocalDateTime.now().minusMinutes(30))) {
order.setStatus(OrderStatus.CLOSED);
inventoryService.restoreStock(order);
} else {
throw new BusinessException("超过30分钟不能取消订单");
}
}
} else if (order.getStatus() == OrderStatus.WAITING_RISK_REVIEW) {
if (event == OrderEvent.RISK_APPROVE) {
order.setStatus(OrderStatus.PAID);
inventoryService.reduceStock(order);
} else if (event == OrderEvent.RISK_REJECT) {
order.setStatus(OrderStatus.CLOSED);
notificationService.notifyUser(order.getUserId(), "风控审核未通过");
}
}
// ... 更多状态和事件的嵌套判断
}
}
1.2 if-else方案的致命缺陷
维护 nightmare:
- 难以理解:新同事需要半天才能理清状态流转
- 容易出错:修改一个分支可能影响其他逻辑
- 测试困难:分支覆盖测试用例指数级增长
- 需求变更痛苦:每次加新状态都要修改多个地方
二、状态机引擎:架构思维的转变
2.1 什么是状态机?
状态机的核心概念很简单:
- 状态:系统所处的状况(如:待支付、已支付)
- 事件:触发状态变更的动作(如:支付成功、取消订单)
- 流转:状态 + 事件 → 新状态
- 动作:状态变更时执行的业务逻辑
2.2 为什么选择状态机?
状态机的优势:
- 可视化:状态流转一目了然
- 可维护性:状态逻辑集中管理
- 可扩展性:新增状态不影响现有逻辑
- 可测试性:每个流转都可以独立测试
3.1 环境配置
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>3.0.0</version>
</dependency>
3.2 定义订单状态机
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderEvent> {
@Override
public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states)
throws Exception {
states
.withStates()
.initial(OrderStatus.WAITING_PAYMENT)
.state(OrderStatus.PAID)
.state(OrderStatus.WAITING_RISK_REVIEW)
.state(OrderStatus.DELIVERED)
.state(OrderStatus.COMPLETED)
.state(OrderStatus.CLOSED)
.state(OrderStatus.CANCELLED);
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions)
throws Exception {
transitions
// 支付相关流转
.withExternal()
.source(OrderStatus.WAITING_PAYMENT)
.target(OrderStatus.WAITING_RISK_REVIEW)
.event(OrderEvent.PAY_SUCCESS)
.action(riskReviewAction())
.guard(amountGreaterThan10000Guard())
.and()
.withExternal()
.source(OrderStatus.WAITING_PAYMENT)
.target(OrderStatus.PAID)
.event(OrderEvent.PAY_SUCCESS)
.action(paymentSuccessAction())
.guard(amountLessThanOrEqual10000Guard())
.and()
.withExternal()
.source(OrderStatus.WAITING_PAYMENT)
.target(OrderStatus.CLOSED)
.event(OrderEvent.PAY_TIMEOUT)
.action(paymentTimeoutAction())
.and()
.withExternal()
.source(OrderStatus.WAITING_PAYMENT)
.target(OrderStatus.CANCELLED)
.event(OrderEvent.USER_CANCEL)
.action(userCancelAction())
.guard(within30MinutesGuard())
// 风控审核流转
.and()
.withExternal()
.source(OrderStatus.WAITING_RISK_REVIEW)
.target(OrderStatus.PAID)
.event(orderEvent.RISK_APPROVE)
.action(riskApproveAction())
.and()
.withExternal()
.source(OrderStatus.WAITING_RISK_REVIEW)
.target(OrderStatus.CLOSed)
.event(orderEvent.RISK_REJECT)
.action(riskRejectAction());
}
@Bean
public Action<OrderStatus, OrderEvent> paymentSuccessAction() {
return context -> {
Order order = context.getExtendedState().get("order", Order.class);
inventoryService.reduceStock(order);
log.info("订单支付成功: {}", order.getOrderNo());
};
}
}
3.3 状态机Guard(条件判断)
@Component
public class OrderGuard {
public Guard<OrderStatus, OrderEvent> amountGreaterThan10000Guard() {
return context -> {
Order order = context.getExtendedState().get("order", Order.class);
return order.getAmount().compareTo(new BigDecimal("10000")) > 0;
};
}
public Guard<OrderStatus, OrderEvent> within30MinutesGuard() {
return context -> {
Order order = context.getExtendedState().get("order", Order.class);
return Duration.between(order.getCreateTime(), LocalDateTime.now())
.toMinutes() <= 30;
};
}
}
3.4 使用状态机处理业务逻辑
@Service
public class OrderStateMachineService {
@Autowired
private StateMachine<OrderStatus, OrderEvent> stateMachine;
@Transactional
public void handleEvent(Long orderId, OrderEvent event) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
// 重置状态机到订单当前状态
stateMachine.stop();
stateMachine.getStateMachineAccessor()
.doWithAllRegions(access -> access.resetStateMachine(
new DefaultStateMachineContext<>(
order.getStatus(), null, null, null)));
stateMachine.start();
// 设置订单到扩展状态
stateMachine.getExtendedState().getVariables().put("order", order);
// 发送事件
boolean accepted = stateMachine.sendEvent(event);
if (accepted) {
// 更新订单状态
OrderStatus newStatus = stateMachine.getState().getId();
order.setStatus(newStatus);
orderRepository.save(order);
} else {
throw new IllegalStateException("状态流转失败: " + order.getStatus() + " -> " + event);
}
}
}
四、更轻量的选择:Cola StateMachine
如果你觉得Spring StateMachine太重,可以试试阿里的Cola StateMachine:
4.1 Cola StateMachine配置
@Configuration
public class ColaStateMachineConfig {
@Bean
public StateMachine<OrderStatus, OrderEvent, OrderContext> orderStateMachine() {
StateMachineBuilder<OrderStatus, OrderEvent, OrderContext> builder =
StateMachineBuilder.create();
// 待支付状态的流转
builder.externalTransition()
.from(OrderStatus.WAITING_PAYMENT)
.to(OrderStatus.WAITING_RISK_REVIEW)
.on(OrderEvent.PAY_SUCCESS)
.when(checkAmountGreaterThan10000())
.perform(doRiskReview());
builder.externalTransition()
.from(OrderStatus.WAITING_PAYMENT)
.to(OrderStatus.PAID)
.on(OrderEvent.PAY_SUCCESS)
.when(checkAmountLessThanOrEqual10000())
.perform(doPaymentSuccess());
return builder.build("orderStateMachine");
}
}
4.2 Cola状态机使用
@Service
public class ColaOrderService {
@Autowired
private StateMachine<OrderStatus, OrderEvent, OrderContext> stateMachine;
public void handleEvent(Order order, OrderEvent event) {
OrderContext context = new OrderContext();
context.setOrder(order);
OrderStatus newStatus = stateMachine.fireEvent(order.getStatus(), event, context);
if (newStatus != null) {
order.setStatus(newStatus);
orderRepository.save(order);
} else {
throw new IllegalStateException("无效的状态流转");
}
}
}
五、可视化:让状态流转一目了然
5.1 生成状态图
使用Graphviz生成状态流转图:
@Component
public class StateMachineVisualizer {
public String generateGraphviz(StateMachine<OrderStatus, OrderEvent> stateMachine) {
StringBuilder dot = new StringBuilder();
dot.append("digraph OrderStateMachine {\n");
dot.append(" rankdir=LR;\n");
dot.append(" node [shape=circle];\n\n");
// 添加状态节点
for (OrderStatus state : OrderStatus.values()) {
dot.append(" ").append(state.name()).append(";\n");
}
dot.append("\n");
// 添加流转边
// 这里需要根据实际配置生成
dot.append(" WAITING_PAYMENT -> WAITING_RISK_REVIEW [label=\"PAY_SUCCESS(amount>10000)\"];\n");
dot.append(" WAITING_PAYMENT -> PAID [label=\"PAY_SUCCESS(amount<=10000)\"];\n");
dot.append(" WAITING_PAYMENT -> CLOSED [label=\"PAY_TIMEOUT\"];\n");
dot.append("}");
return dot.toString();
}
}
5.2 可视化效果
生成的状态图清晰展示了整个业务流程:
WAITING_PAYMENT → PAY_SUCCESS(amount>10000) → WAITING_RISK_REVIEW
WAITING_PAYMENT → PAY_SUCCESS(amount<=10000) → PAID
WAITING_PAYMENT → PAY_TIMEOUT → CLOSED
WAITING_PAYMENT → USER_CANCEL(within30min) → CANCELLED
WAITING_RISK_REVIEW → RISK_APPROVE → PAID
WAITING_RISK_REVIEW → RISK_REJECT → CLOSED
六、测试:状态机的可测试性优势
6.1 单元测试变得简单
@SpringBootTest
class OrderStateMachineTest {
@Autowired
private OrderStateMachineService stateMachineService;
@Test
void should_transition_to_risk_review_when_large_amount_paid() {
// Given
Order order = createOrderWithAmount(new BigDecimal("15000"));
// When
stateMachineService.handleEvent(order.getId(), OrderEvent.PAY_SUCCESS);
// Then
Order updatedOrder = orderRepository.findById(order.getId()).get();
assertThat(updatedOrder.getStatus()).isEqualTo(OrderStatus.WAITING_RISK_REVIEW);
}
@Test
void should_reject_cancel_after_30_minutes() {
// Given
Order order = createOrder30MinutesAgo();
// When & Then
assertThatThrownBy(() ->
stateMachineService.handleEvent(order.getId(), OrderEvent.USER_CANCEL))
.isInstanceOf(IllegalStateException.class);
}
}
七、高级特性:应对复杂业务场景
7.1 分层状态机
对于更复杂的业务,可以使用分层状态机:
public class HierarchicalStateMachineConfig {
public void configureHierarchicalStates(
StateMachineStateConfigurer<OrderStatus, OrderEvent> states)
throws Exception {
states
.withStates()
.initial(OrderStatus.WAITING_PAYMENT)
.state(OrderStatus.PAID)
.state(OrderStatus.DELIVERED)
.and()
.withStates()
.parent(OrderStatus.DELIVERED)
.initial(OrderStatus.WAITING_RECEIVE)
.state(OrderStatus.RECEIVED)
.state(OrderStatus.APPLYING_RETURN)
.state(OrderStatus.RETURNED);
}
}
7.2 状态机持久化
@Component
public class OrderStateMachinePersist
implements StateMachinePersist<OrderStatus, OrderEvent, String> {
@Autowired
private OrderRepository orderRepository;
@Override
public void write(StateMachineContext<OrderStatus, OrderEvent> context, String orderId) {
Order order = orderRepository.findById(Long.valueOf(orderId)).get();
order.setStateMachineContext(objectMapper.writeValueAsString(context));
orderRepository.save(order);
}
@Override
public StateMachineContext<OrderStatus, OrderEvent> read(String orderId) {
Order order = orderRepository.findById(Long.valueOf(orderId)).get();
if (order.getStateMachineContext() != null) {
return objectMapper.readValue(order.getStateMachineContext(),
StateMachineContext.class);
}
return null;
}
}
八、迁移策略:从if-else平滑过渡
8.1 渐进式迁移步骤
- 识别核心状态:找出业务中的主要状态和事件
- 创建状态机配置:先实现核心流转逻辑
- 并行运行:新老逻辑同时存在,逐步切换
- 全面迁移:所有逻辑都迁移到状态机
- 清理老代码:删除原有的if-else逻辑
8.2 迁移后的架构对比
迁移前:
- 代码:2000行if-else逻辑
- 维护:每次修改需要仔细测试所有分支
- 理解:新同事需要2天熟悉代码
迁移后:
- 代码:500行状态机配置 + 300行业务动作
- 维护:修改配置即可,不影响其他逻辑
- 理解:新同事2小时看懂状态图
总结:状态机的适用场景
适合使用状态机的场景:
- ✅ 复杂的业务流程(审批流、订单流、工单流)
- ✅ 明确的状态和事件
- ✅ 频繁的业务规则变更
- ✅ 需要可视化业务流程
可能不适用的场景:
- ❌ 简单的CRUD操作
- ❌ 没有明确状态概念的场景
- ❌ 实时性要求极高的场景(状态机有轻微开销)
我们的收获:
- 代码更清晰:状态流转一目了然
- 维护更简单:新增状态不影响现有逻辑
- 测试更容易:每个流转可以独立测试
- 沟通更高效:产品经理也能看懂状态图
通过引入Java状态机引擎,我们成功地将错综复杂的业务逻辑转化为清晰可管理的状态流转图。这不仅提升了代码质量,也让整个团队的协作效率上了一个台阶。这种设计模式的转变,本质上是从“过程式”思维向“声明式”思维的提升。如果你也在为复杂的业务逻辑头疼,不妨试试用状态机来理清头绪,或许会有意想不到的收获。更多关于架构设计、分布式系统的深度讨论,欢迎在云栈社区的技术论坛中与我们交流。