I have a spring state machine that is tied to states of an object from another service, that is retrieved and updated using HTTP APIs. So, it's not a local database.
I'm thinking to use transition actions to make sure state machine rules are respected, i.e. I need to fetch object from the other service using REST API, call that service REST API to update the object state. If somehow the service is unavailable/return 500, my application's state machine should fail and transition actions shouldn't run.
According to ChatGPT this is the ordering of actions using state machine:
interceptor preEvent
interceptor preStateChange
transition action(s)
state exit actions
state entry actions
interceptor postStateChange
listener transition
I'm trying to avoid repeating the definition of transition action calling REST API to update the object state, so I was trying preStateChange. However, in my logs, the preStateChange occurs after the transition action. Here is my code:
@Service
@RequiredArgsConstructor
public class ObjectStateMachineFactory {
private final StateMachineFactory factory;
private final UpdateObjectStateInterceptor updateObjectStateInterceptor;
public StateMachine getStateMachine() {
StateMachine sm = factory.getStateMachine();
sm.getStateMachineAccessor()
.doWithAllRegions(
access -> access.addStateMachineInterceptor(updateObjectStateInterceptor));
return sm;
}
}
@Slf4j
@Service
@RequiredArgsConstructor
public class ObjectStateMachineService {
private final ObjectStateMachineFactory stateMachineFactory;
private final ObjectService objectService;
private final UpdateObjectInterceptor updateObjectInterceptor;
@Transactional
public boolean processObjectEvent(String id, ObjectEvent event) {
CustomObject obj = objectService.getObject(id);
// TODO: Don't build state machine everytime as its an expensive operation
StateMachine sm = stateMachineFactory.getStateMachine();
try {
sm.startReactively();
// Set initial state using resetStateMachine
CustomObjectState currentState =
CustomObjectState.fromValue(obj.getState().getObj().getKey());
if (sm.getState() == null || sm.getState().getId() != currentState) {
sm.getStateMachineAccessor()
.withRegion()
.resetStateMachineReactively(
new DefaultStateMachineContext<>(currentState, null, null, sm.getExtendedState()))
.block();
}
sm.getExtendedState().getVariables().put("customObj", obj);
boolean success = sm.sendEvent(event);
}
return success;
} finally {
sm.stop();
}
}
}
@Slf4j
@Configuration
@RequiredArgsConstructor
@EnableStateMachineFactory
public class OrderStateConfig extends StateMachineConfigurerAdapter {
@Override
public void configure(StateMachineStateConfigurer states)
throws Exception {
states
.withStates()
.initial(CustomObjectState.UNCONFIRMED)
.states(EnumSet.allOf(CustomObjectState.class));
}
@Override
public void configure(StateMachineConfigurationConfigurer config)
throws Exception {
// IMPORTANT: disable auto-start so interceptor can be attached first
config.withConfiguration().autoStartup(false);
}
@Override
public void configure(StateMachineTransitionConfigurer transitions)
throws Exception {
transitions
// CONFIRM event
.withExternal()
.source(CustomObjectState.A)
.target(CustomObjectState.B)
.event(ObjectEvent.CONFIRM)
.guard(objectStateGuard.canTransitToB())
.action(sendNotificationAction)
.
.
.
I'm expecting the order:
objectStateGuard.canTransitToB();
UpdateObjectStateInterceptor.preStateChange();
sendNotificationAction
but sendNotificationAction is occuring before preStateChange
is there any way to accomplish this, or am I using the wrong feature of state machine? how to use the state machine without local database? thank you