IT评测·应用市场-qidao123.com

标题: Spring Statemachine应用实践 [打印本页]

作者: 宁睿    时间: 2023-5-17 09:06
标题: Spring Statemachine应用实践

 前言 
在日常开发中经常遇到运营审核经销商活动、任务等等类似业务需求,大部分需求中状态稳定且单一无需使用状态机,但是也会出现大量的if...else前置状态代码,也是不够那么的“优雅”。随着业务的发展、需求迭代,每一次的业务代码改动都需要维护使用到状态的代码,更让开发人员头疼的是这些维护状态的代码,像散弹一样遍布在各个Service的方法中,不仅增加发布的风险,同时也增加了回归测试的工作量。
 
1. 什么是状态机?
通常所说的状态机为有限状态机(英语:finite-state machine,缩写:FSM),简称状态机, 是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。 
应用FSM模型可以帮助对象生命周期的状态的顺序以及导致状态变化的事件进行管理。 将状态和事件控制从不同的业务Service方法的if else中抽离出来。FSM的应用范围很广,状态机 可以描述核心业务规则,核心业务内容. 无限状态机,顾名思义状态无限,类似于“π”,暂不做研究。
状态机可归纳为4个要素,即现态、条件、动作、次态。这样的归纳,主要是出于对状态机的内在因果关系的考虑。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:
动作是在给定时刻要进行的活动的描述。有多种类型的动作:
其他术语:
文字描述比较不容易理解,让我们举个栗子:每天上班都需要坐地铁,从刷卡进站到闸机关闭这个过程,将闸机抽象为一个状态机模型,如下图:

 

2. 什么场景使用?
以下的场景您可能会需要使用:
您可以将应用程序或其结构的一部分表示为状态。
您希望将复杂的逻辑拆分为更小的可管理任务。
应用程序已经遇到了并发问题,例如异步执行导致了一些异常情况。
当您执行以下操作时,您已经在尝试实现状态机:
使用布尔标志或枚举来建模情况。
具有仅对应用程序生命周期的某些部分有意义的变量。
在if...else结构(或者更糟糕的是,多个这样的结构)中循环,检查是否设置了特定的标志或枚举,然后在标志和枚举的某些组合存在或不存在时,做出进一步的异常处理。
 
3. 为什么要用?有哪些好处?
最初活动模块功能设计时,并没有想使用状态机,仅仅想把状态的变更和业务剥离开,规范状态转换和程序在不同状态下所能提供的能力,去掉复杂的逻辑判断也就是if...else,想换一种模式实现思路,此前了解过spring“全家桶”有状态机就想到了“它”,场景也符合。
从个人使用的经验,开发阶段和迭代维护期总结了以下几点:
 
4. 实践
java语言状态机框架有很多,目前github star 数比较多的有 spring-statemachine(star 1.3K) 、squirrel-foundation(star1.9K)即“松鼠”状态机,stateless4j相较前两个名气较小,未深入研究。spring-statemachine是spring官方提供的状态机实现,功能强大,但是相对来说很“重”,加载实例的时间也长于squirrel-foundation,不过好在一直都是有更新(目前官方已更新3.2.0),相信会越来越成熟。
实际生产中使用的是spring statemachine ,版本是2.2.0.RELEASE。线下对比使用的是squirrel-foundation,版本是0.3.10。这里仅供使用对比。
从创建活动到活动下线状态流转作为示例,如下图:
 

pom
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <dependency>
  3.     <groupId>org.springframework.statemachine</groupId>
  4.     <artifactId>spring-statemachine-starter</artifactId>
  5.     <version>2.2.0.RELEASE</version>
  6. </dependency>
  7.         
  8. <dependency>
  9. <groupId>org.springframework.statemachine</groupId>
  10. <artifactId>spring-statemachine-kryo</artifactId>
  11. <version>2.2.0.RELEASE</version>
  12. </dependency>
  13.         
  14. <dependency>
  15. <groupId>org.squirrelframework</groupId>
  16. <artifactId>squirrel-foundation</artifactId>
  17. <version>0.3.10</version>
  18. </dependency>
复制代码
状态&事件定义
  1. public enum State {
  2.     INIT("初始化"),
  3.     DRAFT("草稿"),
  4.     WAIT_VERIFY("待审核"),
  5.     PASSED("审核通过"),
  6.     REJECTED("已驳回"),
  7.     //已发起上线操作,未到上线时间的状态
  8.     WAIT_ONLIE("待上线"),
  9.     ONLINED("已上线"),
  10.     //过渡状态无实际意义,无需事件触发
  11.     OFFLINING("下线中"),
  12.     OFFLINED("已下线"),
  13.     FINISHED("已结束");
  14.     private final String desc;
  15. }
  16. public enum Event {
  17.     SAVE("保存草稿"),
  18.     SUBMIT("提交审核"),
  19.     PASS("审核通过"),
  20.     REJECT("提交驳回"),
  21.     ONLINE("上线"),
  22.     OFFLINE("下线"),
  23.     FINISH("结束");
  24.     private final String desc;
  25. }
复制代码
状态流转定义
  1. @Configuration
  2. @EnableStateMachineFactory
  3. public class ActivitySpringStateMachineAutoConfiguration extends StateMachineConfigurerAdapter<State, Event> {
  4.     @Autowired
  5.     private ApplicationContext applicationContext;
  6.     @Autowired
  7.     private StateMachineRuntimePersister<State, Event, String> activityStateMachinePersister;
  8.     @Bean
  9.     public StateMachineService<State, Event> activityStateMachineService(StateMachineFactory<State, Event> stateMachineFactory) {
  10.         return new DefaultStateMachineService<>(stateMachineFactory, activityStateMachinePersister);
  11.     }
  12.     @Override
  13.     public void configure(StateMachineConfigurationConfigurer<State, Event> config) throws Exception {
  14.         // @formatter:off
  15.         config
  16.                 .withPersistence()
  17.                 .runtimePersister(activityStateMachinePersister)
  18.                 .and().withConfiguration()
  19.                 .stateDoActionPolicy(StateDoActionPolicy.TIMEOUT_CANCEL)
  20.                 .stateDoActionPolicyTimeout(300, TimeUnit.SECONDS)
  21.                 .autoStartup(false);
  22.         // @formatter:on
  23.     }
  24.     @Override
  25.     public void configure(StateMachineStateConfigurer<State, Event> states) throws Exception {
  26.         states.withStates()
  27.                 .initial(State.INIT)
  28.                 .choice(State.OFFLINING)
  29.                 .states(EnumSet.allOf(State.class));
  30.     }
  31.     @Override
  32.     public void configure(StateMachineTransitionConfigurer<State, Event> transitions) throws Exception {
  33.         // 待提交审核 --提交审核--> 待审核
  34.         // @formatter:off
  35.         // 现态-->事件-->次态
  36.         transitions.withExternal()
  37.                 .source(State.INIT).target(State.DRAFT).event(Event.SAVE)
  38.                 .and().withExternal()
  39.                 .source(State.DRAFT).target(State.WAIT_VERIFY).event(Event.SUBMIT)
  40.                 .guard(applicationContext.getBean(SubmitCondition.class));
  41.         transitions.withExternal().source(State.WAIT_VERIFY).target(State.PASSED).event(Event.PASS)
  42.                 .action(applicationContext.getBean(PassAction.class));
  43.         transitions.withExternal().source(State.WAIT_VERIFY).target(State.REJECTED).event(Event.REJECT)
  44.                 .guard(applicationContext.getBean(RejectCondition.class));
  45.         transitions.withExternal()
  46.                 .source(State.REJECTED)
  47.                 .target(State.WAIT_VERIFY)
  48.                 .event(Event.SUBMIT)
  49.                 .guard(applicationContext.getBean(SubmitCondition.class));
  50.         // 审核通过-->上线-->待上线
  51.         transitions.withExternal().source(State.PASSED).target(State.WAIT_ONLIE).event(Event.ONLINE);
  52.         // 待上线-->上线-->已上线
  53.         transitions.withExternal().source(State.WAIT_ONLIE).target(State.ONLINED).event(Event.ONLINE);
  54.         // 已上线-->下线-->已下线
  55.         transitions.withExternal()
  56.                 .source(State.ONLINED).target(State.OFFLINING).event(Event.OFFLINE);
  57.         // 待上线-->下线-->下线中
  58.         transitions.withExternal()
  59.                 .source(State.WAIT_ONLIE).target(State.OFFLINING).event(Event.OFFLINE)
  60.                 .and()
  61.                 // 已下线-->结束-->已结束
  62.                 .withChoice()
  63.                 .source(State.OFFLINING)
  64.                 .first(State.FINISHED, new Guard<State, Event>() {
  65.                     @Override
  66.                     public boolean evaluate(StateContext<State, Event> context) {
  67.                         return true;
  68.                     }
  69.                 })
  70.                 .last(State.OFFLINED);
  71.         // @formatter:on
  72.     }
  73. }
复制代码
说明:
Guard与Action
  1. @Component
  2. public class SaveGuard implements Guard<State, Event> {
  3.     @Override
  4.     public boolean evaluate(StateContext<State, Event> context) {
  5.         log.info("[execute save guard]");
  6.         return true;
  7.     }
  8. }
  9. @Component
  10. public class SaveAction implements Action<State, Event> {
  11.     @Override
  12.     public void execute(StateContext<State, Event> context) {
  13.         try {
  14.             log.info("[execute saveAction]");
  15.         } catch (Exception e) {
  16.             context.getExtendedState().getVariables().put("ERROR", e.getMessage());
  17.         }
  18.     }
  19. }
复制代码
说明:
持久化配置
  1. @Component
  2. public class ActivityStateMachinePersister extends AbstractStateMachineRuntimePersister<State, Event, String> {
  3.     @Autowired
  4.     private ActivityStateService activityStateService;
  5.     @Override
  6.     public void write(StateMachineContext<State, Event> context, String id) {
  7.         Activity state = new Activity();
  8.         state.setMachineId(id);
  9.         state.setState(context.getState());
  10.         activityStateService.save(state);
  11.     }
  12.     @Override
  13.     public StateMachineContext<State, Event> read(String id) {
  14.         return deserialize(activityStateService.getContextById(id));
  15.     }
  16. }
复制代码
说明:
状态服务调用
  1. @Service
  2. public class StateTransitService {
  3.     @Autowired
  4.     private StateMachineService<State, Event> stateMachineService;
  5.     @Transactional
  6.     public void transimit(String machineId, Message<Event> message) {
  7.         StateMachine<State, Event> stateMachine = stateMachineService.acquireStateMachine(machineId);
  8.         stateMachine.addStateListener(new DefaultStateMachineListener<>(stateMachine));
  9.         stateMachine.sendEvent(message);
  10.         if (stateMachine.hasStateMachineError()) {
  11.             String errorMessage = stateMachine.getExtendedState().get("message", String.class);
  12.             stateMachineService.releaseStateMachine(machineId);
  13.             throw new ResponseException(errorMessage);
  14.         }
  15.     }
  16. }
  17. @AllArgsConstructor
  18. public class DefaultStateMachineListener<S, E> extends StateMachineListenerAdapter<S, E> {
  19.     private final StateMachine<S, E> stateMachine;
  20.     @Override
  21.     public void eventNotAccepted(Message<E> event) {
  22.         stateMachine.getExtendedState().getVariables().put("message", "当前状态不满足执行条件");
  23.         stateMachine.setStateMachineError(new ResponseException(500, "Event not accepted"));
  24.     }
  25.     @Override
  26.     public void transitionEnded(Transition<S, E> transition) {
  27.         log.info("source {} to {}", transition.getSource().getId(), transition.getTarget().getId());
  28.     }
  29. }
复制代码
说明:
集成单元测试
  1. @SpringBootTest
  2. @RunWith(SpringRunner.class)
  3. public class StateMachineITest {
  4.     @Autowired
  5.     private StateTransitService transmitService;
  6.     @Autowired
  7.     private ActivityStateService activityStateService;
  8.     @Test
  9.     public void test() {
  10.         String machineId = "test";//业务主键ID
  11.         transmitService.transimit(machineId, MessageBuilder.withPayload(Event.SAVE).build());
  12.         transmitService.transimit(machineId, MessageBuilder.withPayload(Event.SUBMIT).build());
  13.         transmitService.transimit(machineId, MessageBuilder.withPayload(Event.PASS).build());
  14.         transmitService.transimit(machineId, MessageBuilder.withPayload(Event.ONLINE).build());
  15.         transmitService.transimit(machineId, MessageBuilder.withPayload(Event.ONLINE).build());
  16.         transmitService.transimit(machineId, MessageBuilder.withPayload(Event.OFFLINE).build());
  17.         assert activityStateService.getStateById(machineId).equals(State.FINISHED);
  18.     }
  19. }
复制代码
 

注意事项
 
扩展-与squirrel-foundation异同
  1. @Component
  2. public class ActivityMachine extends SquirrelStateMachine<ActivityMachine, State, Event, TransmitCmd> {
  3.     private final ActivityStateService activityStateService;
  4.     public ActivityMachine(ApplicationContext applicationContext) {
  5.         super(applicationContext);
  6.         activityStateService = applicationContext.getBean(ActivityStateService.class);
  7.     }
  8.     @Override
  9.     public void buildStateMachine(StateMachineBuilder<ActivityMachine, State, Event, TransmitCmd> stateMachineBuilder) {
  10.         stateMachineBuilder.externalTransition().from(State.INIT).to(State.DRAFT).on(Event.SAVE).when(applicationContext.getBean(SubmitCondition.class));
  11.         //以下省略,大致与spring-statemachine相同
  12.     }
  13.     @Override
  14.     public ActivityMachine createStateMachine(State stateId) {
  15.         ActivityMachine activityMachine = super.createStateMachine(stateId);
  16.         activityMachine.addStartListener(new StartListener<ActivityMachine, State, Event, TransmitCmd>() {
  17.         });
  18.         return activityMachine;
  19.     }
  20.     @Override
  21.     protected void afterTransitionDeclined(S fromState, E event, C context) {
  22.         //转移状态未执行
  23.     }
  24.     @Override
  25.     protected void afterTransitionCausedException(S fromState, S toState, E event, C context) {
  26.         // 转移状态时发生异常
  27.     }
  28.     @Override
  29.     protected void afterTransitionCompleted(State fromState, State toState, Event event, TransmitCmd context) {
  30.         log.info("from {} to {} on {}, {}", fromState.getDesc(), toState.getDesc(), event.getDesc(), context);
  31.     }
  32. }
复制代码
说明:
5.使用后的效果如何?
以下是在开发和迭代维护期间,真切体会到状态机带来好处的两个小场景。
1.由于新项目中涉及到跨部门卡券业务,在开发初期审核活动通过时同步创建卡券批次,却忽略了异步生成券码的时间,随着开发的深入才意识到此问题。此时只需要在状态审核通过时加一个过渡状态并启动一个任务去轮询券码是否创建完成即可,丝毫不影响已开发的代码。
2.最初的需求设计时,活动下线后是不能再次上线的,在需求迭代期内又增加了再次上线的功能,状态机流转逻辑清晰,只需要再增加个状态配置流转事件就行,就为状态机赋予了再次上线的能力。
 
6.总结 
在实践的过程中,在spring-statemachine官方文档结合Google摸索使用的过程中,遇到持久化存储StateMachineContext、异常处理,以及状态分支等问题。目前回头看来也不复杂,如今写出来总结一下,希望对小伙伴们有所帮助。
最后建议在状态流程不是很复杂的情况,如果您也厌烦了if...else,那么不妨尝试一下squirrel-foundation,相信也是不错的选择。
 
参考文献
作者|姜强强

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4