Spring state machine StateMachine framework - enterprise development practice (including code)
Recently, in my work, my superior sent me a task and asked me to write down the state machine. At first, I was directly ignorant. I didn't know what a state machine was. After listening to the business requirements, I slowly understood that the function of this state machine is to modify the order state in order to make the business code reuse efficiently. This leads to the second question, how to implement the state machine? Is it to write a pile of if else judgments? At first, my idea was like this. Later, I checked the state machine on the Internet and found a StateMachine framework. Then I went to see the official documents https://docs.spring.io/spring-statemachine/docs/2.0.2.RELEASE/reference/htmlsingle/#with-enablestatemachinefactory. Of course, I stepped on a lot of pits in the process of use, so I wrote down some of my experience. Of course, I haven't read any source code, but I can only have some superficial opinions. For reference only.
1, Status flow chart
I think this flowchart is very important. It is very important to know the flow of each state and what event will trigger what event flow. The following is a status flow chart of my own work
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-olhxfhjh-1631519370357) (D: \ mydata \ ligy112 \ appdata \ roaming \ typora user images \ image-20210913135419684. PNG)]
2, Understand the following basic common components
.withExternal() .source(TradeOrderStateMachineEnum.WAIT_FOR_PAY) .target(TradeOrderStateMachineEnum.CLOSED) .event(TradeOrderEvent.CLOSE).and()
This is the configuration rule, which means that from wait_ FOR_ Pay - > CLOSED, which needs to be triggered by the CLOSE event. This is a relatively simple one. It flows directly to the CLOSED state without guard judgment.
.withExternal() .source(TradeOrderStateMachineEnum.WAIT_FOR_AUDIT) .target(TradeOrderStateMachineEnum.WAIT_FOR_DELIVER) .event(TradeOrderEvent.AUDIT).guard(tradeOrderGuardFactory.new TradeOrderGuard()).and()
In the following one, there is an additional. guard(), which is a judgment, which is equivalent to the judgment condition of if in java. This custom judgment class must implement the guard interface and rewrite the evaluate method, which returns boolean. It is worth mentioning that each rule can be configured with action, which can be directly followed by. action(), or you can write your own business code with @ WithStateMachine and @ OnTransition annotations. This action indicates that only after the entire link rules are met can we do.
.withChoice() .source(TradeOrderStateMachineEnum.AUDIT_CHOICE) .first(TradeOrderStateMachineEnum.COMPLETED, tradeOrderGuardFactory.new TradeOrderAuditChoiceGuard(),new TradeOrderChoiceAction()) .then(TradeOrderStateMachineEnum.WAIT_FOR_EVALUATE, tradeOrderGuardFactory.new TradeOrderAuditChoiceGuard2(),new TradeOrderChoiceAction()) .then(TradeOrderStateMachineEnum.WAIT_FOR_SIGN, tradeOrderGuardFactory.new TradeOrderAuditChoiceGuard3(),new TradeOrderChoiceAction()) .then(TradeOrderStateMachineEnum.WAIT_FOR_DELIVER, tradeOrderGuardFactory.new TradeOrderAuditChoiceGuard4(),new TradeOrderChoiceAction()) .last(TradeOrderStateMachineEnum.WAIT_FOR_AUDIT).and()
The first two are relatively simple. An event can only flow to one state. The above is a complex point and is often used in business. An event will have several States, first then last, which is equivalent to if else if. As long as a guard judgment is satisfied, it will not flow down. Note that there are several pits here, which will be described later.
3, Code
3.1 importing dependent packages
<dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-starter</artifactId> <version>2.0.1.RELEASE</version> </dependency>
3.2 define status enumeration and event enumeration
TradeOrderStateMachineEnum
public enum TradeOrderStateMachineEnum { WAIT_FOR_PAY(10, "Pending payment"), WAIT_FOR_AUDIT(20, "To be reviewed"), WAIT_FOR_DELIVER(30, "To be shipped"), WAIT_FOR_SIGN(40, "To be signed"), WAIT_FOR_EVALUATE(45, "To be evaluated"), COMPLETED(98, "complete"), CLOSED(99, "close"), AUDIT_CHOICE(1000, "Review selection status"), SIGN_CHOICE(1001, "Sign in selection status"); private final Integer value; private final String desc; private static final Map<Integer, TradeOrderStateMachineEnum> valueMap = (Map) Arrays.stream(values()).collect(Collectors.toMap(TradeOrderStateMachineEnum::getValue, Function.identity())); private TradeOrderStateMachineEnum(Integer value, String desc) { this.value = value; this.desc = desc; } public Integer getValue() { return this.value; } public String getDesc() { return this.desc; } public static TradeOrderStateMachineEnum fromValue(Integer value) { return (TradeOrderStateMachineEnum) Optional.ofNullable(valueMap.get(value)).orElseThrow(() -> { return new RuntimeException("can not find the enum for this value: " + value); }); } }
TradeOrderEvent
public enum TradeOrderEvent { PAY,//payment CLOSE,//Close order CANCEL,//Cancel quantity AUDIT,//review DELIVER,//deliver goods SIGN,//Sign for EVALUATE;//evaluate }
3.2 defining state machine rules and configuring state machines
TradeOrderStateMachineBuilder
@Component @EnableStateMachine(name= TradeOrderStateMachineBuilder.MACHINEID_TO) public class TradeOrderStateMachineBuilder { private static final TradeOrderGuardFactory tradeOrderGuardFactory= new TradeOrderGuardFactory(); @Autowired private BeanFactory beanFactory; private Logger logger = LoggerFactory.getLogger(getClass()); public final static String MACHINEID_TO = "MACHINEID_TO";//TO state machine public StateMachine<TradeOrderStateMachineEnum, TradeOrderEvent> build() throws Exception { StateMachine<TradeOrderStateMachineEnum, TradeOrderEvent> stateMachine = build(beanFactory); logger.info("State machine ID: "+stateMachine.getId()); stateMachine.start(); return stateMachine; } /** * Build state machine * -Build TO single state machine * @param beanFactory * @return * @throws Exception */ public StateMachine<TradeOrderStateMachineEnum, TradeOrderEvent> build(BeanFactory beanFactory) throws Exception { StateMachineBuilder.Builder<TradeOrderStateMachineEnum, TradeOrderEvent> builder = StateMachineBuilder.builder(); builder.configureConfiguration() .withConfiguration() .machineId(MACHINEID_TO) .beanFactory(beanFactory); builder.configureStates() .withStates() .initial(TradeOrderStateMachineEnum.WAIT_FOR_PAY) .choice(TradeOrderStateMachineEnum.AUDIT_CHOICE) .choice(TradeOrderStateMachineEnum.SIGN_CHOICE) .states(EnumSet.allOf(TradeOrderStateMachineEnum.class)); builder.configureTransitions() //After payment, from pending payment to pending approval .withExternal() .source(TradeOrderStateMachineEnum.WAIT_FOR_PAY) .target(TradeOrderStateMachineEnum.WAIT_FOR_AUDIT) .event(TradeOrderEvent.PAY).and() //Cancel order, from pending payment to closing .withExternal() .source(TradeOrderStateMachineEnum.WAIT_FOR_PAY) .target(TradeOrderStateMachineEnum.CLOSED) .event(TradeOrderEvent.CLOSE).and() //Cancel quantity, from to be approved to approved status .withExternal() .source(TradeOrderStateMachineEnum.WAIT_FOR_AUDIT) .target(TradeOrderStateMachineEnum.AUDIT_CHOICE) .event(TradeOrderEvent.CANCEL).and() //Cancel quantity, from approval status - > to be shipped, to be signed in, to be evaluated, to complete any status .withChoice() .source(TradeOrderStateMachineEnum.AUDIT_CHOICE) .first(TradeOrderStateMachineEnum.COMPLETED, tradeOrderGuardFactory.new TradeOrderAuditChoiceGuard(),new TradeOrderChoiceAction()) .then(TradeOrderStateMachineEnum.WAIT_FOR_EVALUATE, tradeOrderGuardFactory.new TradeOrderAuditChoiceGuard2(),new TradeOrderChoiceAction()) .then(TradeOrderStateMachineEnum.WAIT_FOR_SIGN, tradeOrderGuardFactory.new TradeOrderAuditChoiceGuard3(),new TradeOrderChoiceAction()) .then(TradeOrderStateMachineEnum.WAIT_FOR_DELIVER, tradeOrderGuardFactory.new TradeOrderAuditChoiceGuard4(),new TradeOrderChoiceAction()) .last(TradeOrderStateMachineEnum.WAIT_FOR_AUDIT).and() //After approval, from pending approval to pending shipment .withExternal() .source(TradeOrderStateMachineEnum.WAIT_FOR_AUDIT) .target(TradeOrderStateMachineEnum.WAIT_FOR_DELIVER) .event(TradeOrderEvent.AUDIT).guard(tradeOrderGuardFactory.new TradeOrderGuard()).and() //After delivery, from waiting for delivery to waiting for receipt .withExternal() .source(TradeOrderStateMachineEnum.WAIT_FOR_DELIVER) .target(TradeOrderStateMachineEnum.WAIT_FOR_SIGN) .event(TradeOrderEvent.DELIVER).guard(tradeOrderGuardFactory.new TradeOrderGuard()).and() //After signing in, select from to sign in to sign in .withExternal() .source(TradeOrderStateMachineEnum.WAIT_FOR_SIGN) .target(TradeOrderStateMachineEnum.SIGN_CHOICE) .event(TradeOrderEvent.SIGN).and() //After signing in, from the status of "to be signed in" to "to be evaluated" or "completed" .withChoice() .source(TradeOrderStateMachineEnum.SIGN_CHOICE) .first(TradeOrderStateMachineEnum.WAIT_FOR_EVALUATE, tradeOrderGuardFactory.new TradeOrderSignChoiceGuard(),new TradeOrderChoiceAction()) .then(TradeOrderStateMachineEnum.COMPLETED, tradeOrderGuardFactory.new TradeOrderSignChoiceGuard2(),new TradeOrderChoiceAction()) .last(TradeOrderStateMachineEnum.WAIT_FOR_SIGN).and() //After evaluation, from to be evaluated to completed .withExternal() .source(TradeOrderStateMachineEnum.WAIT_FOR_EVALUATE) .target(TradeOrderStateMachineEnum.COMPLETED) .event(TradeOrderEvent.EVALUATE); return builder.build(); } @Bean(name = "tradeOrderStateMachinePersister") public StateMachinePersister<TradeOrderStateMachineEnum, TradeOrderEvent, TradeOrder> getOrderPersister() { return new DefaultStateMachinePersister<>(new StateMachinePersist<TradeOrderStateMachineEnum, TradeOrderEvent, TradeOrder>() { @Override public void write(StateMachineContext<TradeOrderStateMachineEnum, TradeOrderEvent> context, TradeOrder contextObj) { } @Override public StateMachineContext<TradeOrderStateMachineEnum, TradeOrderEvent> read(TradeOrder contextObj) { StateMachineContext<TradeOrderStateMachineEnum, TradeOrderEvent> result = new DefaultStateMachineContext(TradeOrderStateMachineEnum.fromValue(contextObj.getOrderState()), null, null, null, null, MACHINEID_TO); return result; } ; }); } }
3.3 configure guard judgment class
This part can be optimized, because I read the documents on the official website. For each judgment, I need to create a new guard class and override the evaluate method. I don't want to build too many classes, so I integrate them into one class. I originally intended to use the factory mode to create a new guard class, but my development experience is not very rich.
TradeOrderGuardFactory
public class TradeOrderGuardFactory { private static final String TRADE_ORDER = StateMachineHeaderNameConstants.TRADE_ORDER; public class TradeOrderAuditChoiceGuard implements Guard<TradeOrderStateMachineEnum, TradeOrderEvent> { @Override public boolean evaluate(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context) { TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); if(isAllSign(tradeOrder) && !StringUtils.isEmpty(tradeOrder.getBuyer().getBuyerTenantCode())){ return true; } return false; } } public class TradeOrderAuditChoiceGuard2 implements Guard<TradeOrderStateMachineEnum, TradeOrderEvent> { @Override public boolean evaluate(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context) { TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); if(isAllSign(tradeOrder) && StringUtils.isEmpty(tradeOrder.getBuyer().getBuyerTenantCode())){ return true; } return false; } } public class TradeOrderAuditChoiceGuard3 implements Guard<TradeOrderStateMachineEnum, TradeOrderEvent> { @Override public boolean evaluate(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context) { TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); if(isAllDeliver(tradeOrder)){ return true; } return false; } } public class TradeOrderAuditChoiceGuard4 implements Guard<TradeOrderStateMachineEnum, TradeOrderEvent> { @Override public boolean evaluate(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context) { TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); if(isAllAudit(tradeOrder) ){ return true; } return false; } } public class TradeOrderSignChoiceGuard implements Guard<TradeOrderStateMachineEnum, TradeOrderEvent> { @Override public boolean evaluate(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context) { TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); if(isAllSign(tradeOrder) && StringUtils.isEmpty(tradeOrder.getBuyer().getBuyerTenantCode())){ return true; } return false; } } public class TradeOrderSignChoiceGuard2 implements Guard<TradeOrderStateMachineEnum, TradeOrderEvent> { @Override public boolean evaluate(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context) { TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); if(isAllSign(tradeOrder) && !StringUtils.isEmpty(tradeOrder.getBuyer().getBuyerTenantCode())){ return true; } return false; } } public class TradeOrderGuard implements Guard<TradeOrderStateMachineEnum, TradeOrderEvent> { @Override public boolean evaluate(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context) { boolean result=false; System.out.println(context.getSource().getId()); System.out.println(context.getTarget().getId()); switch (context.getTarget().getId()) { case WAIT_FOR_DELIVER: return WAIT_FOR_DELIVER(context); case WAIT_FOR_SIGN: return WAIT_FOR_SIGN(context); case SIGN_CHOICE: return SIGN_CHOICE(context); case WAIT_FOR_EVALUATE: return WAIT_FOR_EVALUATE(context); case COMPLETED: return COMPLETED(context); default: break; } return result; } private boolean WAIT_FOR_DELIVER(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context){ TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); if(isAllAudit(tradeOrder)){ return true; } return false; } private boolean WAIT_FOR_SIGN(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context){ TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); if(isAllDeliver(tradeOrder)){ return true; } return false; } private boolean SIGN_CHOICE(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context){ TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); if(isAllSign(tradeOrder)){ return true; } return false; } private boolean WAIT_FOR_EVALUATE(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context){ TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); if(isAllSign(tradeOrder)&& StringUtils.isEmpty(tradeOrder.getBuyer().getBuyerTenantCode())){ return true; } return false; } private boolean COMPLETED(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context){ TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); if(isAllSign(tradeOrder)&& !StringUtils.isEmpty(tradeOrder.getBuyer().getBuyerTenantCode())){ return true; } return false; } } private boolean isAllAudit(TradeOrder tradeOrder) { for (TradeOrderDetail tradeOrderDetail : tradeOrder.getTradeOrderDetailList()) { if(ObjectUtils.isEmpty(tradeOrderDetail.getCancelQty())){ tradeOrderDetail.setCancelQty(0); } if(ObjectUtils.isEmpty(tradeOrderDetail.getAuditQty())){ tradeOrderDetail.setAuditQty(0); } //Quantity to be reviewed if(tradeOrderDetail.getItemQty()-tradeOrderDetail.getCancelQty()-tradeOrderDetail.getAuditQty()!=0){ return false; } } return true; } private boolean isAllDeliver(TradeOrder tradeOrder) { for (TradeOrderDetail tradeOrderDetail : tradeOrder.getTradeOrderDetailList()) { if(ObjectUtils.isEmpty(tradeOrderDetail.getCancelQty())){ tradeOrderDetail.setCancelQty(0); } if(ObjectUtils.isEmpty(tradeOrderDetail.getDeliverQty())){ tradeOrderDetail.setDeliverQty(0); } //Quantity to be reviewed if(tradeOrderDetail.getItemQty()-tradeOrderDetail.getCancelQty()-tradeOrderDetail.getDeliverQty()!=0){ return false; } } return true; } private boolean isAllSign(TradeOrder tradeOrder) { for (TradeOrderDetail tradeOrderDetail : tradeOrder.getTradeOrderDetailList()) { if(ObjectUtils.isEmpty(tradeOrderDetail.getCancelQty())){ tradeOrderDetail.setCancelQty(0); } if(ObjectUtils.isEmpty(tradeOrderDetail.getCustSignQty())){ tradeOrderDetail.setCustSignQty(0); } //Signed in quantity if((tradeOrderDetail.getItemQty()-tradeOrderDetail.getCancelQty()-tradeOrderDetail.getCustSignQty()!=0)){ return false; } } return true; } }
3.4 action class
There are two methods, one is annotation, and the other is to implement the Action class to override the execute method. As for why, we will talk about them later, which is also one of them.
TradeOrderChoiceAction
@Slf4j public class TradeOrderChoiceAction implements Action<TradeOrderStateMachineEnum, TradeOrderEvent> { private static final String TRADE_ORDER = StateMachineHeaderNameConstants.TRADE_ORDER; @Override public void execute(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context) { System.out.println(context.getTarget().getId()); switch (context.getTarget().getId()) { case AUDIT_CHOICE: AUDIT_CHOICE(context); break; case SIGN_CHOICE: SIGN_CHOICE(context); break; default: break; } } private void SIGN_CHOICE(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context) { TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); log.info("Before the sign in event, the status of the order is:{}"+tradeOrder.getOrderState()); if(isAllSign(tradeOrder)){ //If it is all signed in and 2C, it is in the status to be evaluated if(StringUtils.isEmpty(tradeOrder.getBuyer().getBuyerTenantCode())){ tradeOrder.setOrderState(TradeOrderStateMachineEnum.WAIT_FOR_EVALUATE.getValue()); }else{ //If it is all signed in and 2B, it is in the completed status tradeOrder.setOrderState(TradeOrderStateMachineEnum.COMPLETED.getValue()); } } log.info("After the sign in event, the status of the order is:{}"+tradeOrder.getOrderState()); } private void AUDIT_CHOICE(StateContext<TradeOrderStateMachineEnum, TradeOrderEvent> context) { TradeOrder tradeOrder = context.getMessage().getHeaders().get(TRADE_ORDER, TradeOrder.class); log.info("Before the cancel quantity event, the status of the order is:{}"+tradeOrder.getOrderState()); //If all are signed in, it may be in the status to be evaluated or completed if(isAllSign(tradeOrder)){ //2C, it is the state to be evaluated if(StringUtils.isEmpty(tradeOrder.getBuyer().getBuyerTenantCode())){ tradeOrder.setOrderState(TradeOrderStateMachineEnum.WAIT_FOR_EVALUATE.getValue()); }else{ //2B, it is completed tradeOrder.setOrderState(TradeOrderStateMachineEnum.COMPLETED.getValue()); } }else if(isAllDeliver(tradeOrder)){ //If all goods are shipped, it is signed in tradeOrder.setOrderState(TradeOrderStateMachineEnum.WAIT_FOR_SIGN.getValue()); }else if(isAllAudit(tradeOrder)){ //If all are approved, it is in the status of pending shipment tradeOrder.setOrderState(TradeOrderStateMachineEnum.WAIT_FOR_DELIVER.getValue()); } log.info("After the cancel quantity event, the status of the order is:{}"+tradeOrder.getOrderState()); } private boolean isAllAudit(TradeOrder tradeOrder) { for (TradeOrderDetail tradeOrderDetail : tradeOrder.getTradeOrderDetailList()) { if(ObjectUtils.isEmpty(tradeOrderDetail.getCancelQty())){ tradeOrderDetail.setCancelQty(0); } if(ObjectUtils.isEmpty(tradeOrderDetail.getAuditQty())){ tradeOrderDetail.setAuditQty(0); } //Quantity to be reviewed if(tradeOrderDetail.getItemQty()-tradeOrderDetail.getCancelQty()-tradeOrderDetail.getAuditQty()!=0){ return false; } } return true; } private boolean isAllDeliver(TradeOrder tradeOrder) { for (TradeOrderDetail tradeOrderDetail : tradeOrder.getTradeOrderDetailList()) { if(ObjectUtils.isEmpty(tradeOrderDetail.getCancelQty())){ tradeOrderDetail.setCancelQty(0); } if(ObjectUtils.isEmpty(tradeOrderDetail.getDeliverQty())){ tradeOrderDetail.setDeliverQty(0); } //Quantity to be reviewed if(tradeOrderDetail.getItemQty()-tradeOrderDetail.getCancelQty()-tradeOrderDetail.getDeliverQty()!=0){ return false; } } return true; } private boolean isAllSign(TradeOrder tradeOrder) { for (TradeOrderDetail tradeOrderDetail : tradeOrder.getTradeOrderDetailList()) { if(ObjectUtils.isEmpty(tradeOrderDetail.getCancelQty())){ tradeOrderDetail.setCancelQty(0); } if(ObjectUtils.isEmpty(tradeOrderDetail.getCustSignQty())){ tradeOrderDetail.setCustSignQty(0); } //Signed in quantity if((tradeOrderDetail.getItemQty()-tradeOrderDetail.getCancelQty()-tradeOrderDetail.getCustSignQty()!=0)){ return false; } } return true; } }
TradeOrderAction
@WithStateMachine(id= TradeOrderStateMachineBuilder.MACHINEID_TO) public class TradeOrderAction { private static final String TRADE_ORDER = StateMachineHeaderNameConstants.TRADE_ORDER; @OnTransition(source = "WAIT_FOR_PAY", target = "WAIT_FOR_AUDIT") public void CUSTOMER_PAY(Message<TradeOrderEvent> message) { TradeOrder tradeOrder = (TradeOrder) message.getHeaders().get(TRADE_ORDER); tradeOrder.setOrderState(TradeOrderStateMachineEnum.WAIT_FOR_AUDIT.getValue()); } @OnTransition(source = "WAIT_FOR_PAY", target = "CLOSED") public void CUSTOMER_CLOSE(Message<TradeOrderEvent> message) { TradeOrder tradeOrder = (TradeOrder) message.getHeaders().get(TRADE_ORDER); tradeOrder.setOrderState(TradeOrderStateMachineEnum.CLOSED.getValue()); } @OnTransition(source = "WAIT_FOR_AUDIT", target = "WAIT_FOR_DELIVER") public void CUSTOMER_AUDIT(Message<TradeOrderEvent> message) { TradeOrder tradeOrder = (TradeOrder) message.getHeaders().get(TRADE_ORDER); tradeOrder.setOrderState(TradeOrderStateMachineEnum.WAIT_FOR_DELIVER.getValue()); } @OnTransition(source = "WAIT_FOR_DELIVER", target = "WAIT_FOR_SIGN") public void CUSTOMER_DELIVER(Message<TradeOrderEvent> message) { TradeOrder tradeOrder = (TradeOrder) message.getHeaders().get(TRADE_ORDER); tradeOrder.setOrderState(TradeOrderStateMachineEnum.WAIT_FOR_SIGN.getValue()); } @OnTransition(source = "WAIT_FOR_EVALUATE", target = "COMPLETED") public void CUSTOMER_EVALUATE(Message<TradeOrderEvent> message) { TradeOrder tradeOrder = (TradeOrder) message.getHeaders().get(TRADE_ORDER); tradeOrder.setOrderState(TradeOrderStateMachineEnum.COMPLETED.getValue()); } }
3.5 other categories
This class is used to send message s to ensure header consistency. Of course, it can also be hard coded to ensure consistency.
StateMachineHeaderNameConstants
public class StateMachineHeaderNameConstants { //Transaction order public static final String TRADE_ORDER = "tradeOrder"; }
3.6 tools used
StateMachineUtils
@Slf4j public class StateMachineUtils { private static final String TRADE_ORDER = StateMachineHeaderNameConstants.TRADE_ORDER; @Autowired private TradeOrderStateMachineBuilder tradeOrderStateMachineBuilder; @Resource(name = "tradeOrderStateMachinePersister") private StateMachinePersister tradeOrderStateMachinePersister; public void execute(TradeOrder tradeOrder, TradeOrderEvent event) throws Exception{ log.debug("The order status before calling the state machine is>>>>>>>>>>{}"+tradeOrder.getOrderState()); //Get TO state machine StateMachine<TradeOrderStateMachineEnum, TradeOrderEvent> stateMachine = tradeOrderStateMachineBuilder.build(); Message message = MessageBuilder.withPayload(event).setHeader(TRADE_ORDER, tradeOrder).build(); //Initialize state machine tradeOrderStateMachinePersister.restore(stateMachine,tradeOrder); stateMachine.sendEvent(message); log.debug("The order status after calling the state machine is>>>>>>>>>>{}"+tradeOrder.getOrderState()); } }
Here's why StateMachinePersister is used for persistence. Of course, redis persistence is also used. I didn't write it if I didn't use it. The reference here is to initialize the state of the state machine, so that the state machine can be the state you want as soon as it comes in. Otherwise, the circulation rules cannot be triggered.
4, Some problems (the pit I stepped on)
4.1 when using withChoice, you must add choice to the initialization, otherwise it will not take effect.
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-vjjsm7rm-1631519370359) (D: \ mydata \ ligy112 \ appdata \ roaming \ typora user images \ image-20210913151753492. PNG)]
Remember to configure the corresponding status here.
4.2 trigger of withchoice
The validation of withChoice is that after the previous withExternal process, if the status in withChoice is directly set, guard and action cannot be executed. Therefore, there is an intermediate state on my side, which is to trigger flow judgment.
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-in3dclwi-1631519370361) (D: \ mydata \ ligy112 \ appdata \ roaming \ typora \ user images \ image-20210913152331982. PNG)]
4.3 target problem in guard.
In general, we can use switch to judge. We don't need to create a pile of guard classes, but the status in withChoice's first, then and lat is after circulation, but we can't use this judgment. We can print the log and try it. This is why I have many inner classes in GuardFactory.
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-kdn8bvip-1631519370362) (D: \ mydata \ ligy112 \ appdata \ roaming \ typora user images \ image-20210913152927415. PNG)]
4.4 the action event of withchoice cannot be triggered by annotation.
In fact, this problem is a bit like the previous one. If you use annotation, there will generally be a target, but the target in withChoice is the target of the previous withExternal, so it will not take effect. The following figure is the general action configuration annotation.
@OnTransition(source = "WAIT_FOR_PAY", target = "WAIT_FOR_AUDIT")
So this is why there are two kinds of actions.
5, Summary
Remember to configure the corresponding state.
4.2 trigger of withchoice
The validation of withChoice is that after the previous withExternal process, if the status in withChoice is directly set, guard and action cannot be executed. Therefore, there is an intermediate state on my side, which is to trigger flow judgment.
[external chain picture transferring... (img-IN3DCLWI-1631519370361)]
4.3 target problem in guard.
In general, we can use switch to judge. We don't need to create a pile of guard classes, but the status in withChoice's first, then and lat is after circulation, but we can't use this judgment. We can print the log and try it. This is why I have many inner classes in GuardFactory.
[external chain picture transferring... (img-kDN8BvIp-1631519370362)]
4.4 the action event of withchoice cannot be triggered by annotation.
In fact, this problem is a bit like the previous one. If you use annotation, there will generally be a target, but the target in withChoice is the target of the previous withExternal, so it will not take effect. The following figure is the general action configuration annotation.
@OnTransition(source = "WAIT_FOR_PAY", target = "WAIT_FOR_AUDIT")
So this is why there are two kinds of actions.
5, Summary
StateMachine is still very good. It can tone a lot of ifelse. More importantly, it can decouple the business. Of course, the above are the problems I encountered in my actual development. I just want to record them. What's wrong? Please point out that after all, I'm also a rookie. This is just my personal note.