【问题标题】:Persist state information using a StateMachine in Spring Boot在 Spring Boot 中使用 StateMachine 持久化状态信息
【发布时间】:2019-09-30 15:39:08
【问题描述】:

我正在做一个项目,我们使用状态机来实现工作流。我在对已实施的内容感到满意时遇到了一些麻烦,我想看看是否有更好的设计/实现来解决我的问题。
我将尝试展示我们目前拥有的东西。

请暂时忽略process_agent,我想只关注process_state 只是开始。我只是想创建一个进程,状态机将立即从 CREATED 转换为 ASSIGNED 并将该状态保存在 Entity 表中(默认情况下,我暂时将当前用户设置为代理)。

有一个表Entity有两个信息:process_agentprocess_state
目前只有三个States,定义为Enums:CREATEDASSIGNEDIN_PROCESS
目前只有两个Events,定义为Enums:ASSIGN_TO_AGENTSTART_PROCESS

控制器中有一个端点,用于创建一个简单地移交给服务的进程:

// In the Controller
// mapper is a MapStruct mapper, it simply copies fields from view to entity and vice versa
ResponseEntity<EntityView> create(@RequestBody final EntityView view) {
  final Entity createdEntity = service.create(entityView);
  final EntityView createdEntityView = mapper.toView(createdEntity); //map the entity to its view
  return status(CREATED).body(createdEntityView);
}


// In the Service 
// mapper is a MapStruct mapper, it simply copies fields from view to entity and vice versa
// stateHandler is a custom class to handle an event, see below
Entity entity = new Entity();
mapper.updateFromView(entityView, entity);
entity.setInitState(CREATED);
final Message<Event> message = MessageBuilder.withPayload(Event.ASSIGN_TO_AGENT).setHeader("ENTITY_HEADER", entity);
stateHandler.handleEvent(message);
entity.setProcessAgent(...get the current user's id somehow...);
...
return entity;

StateHandler 处理事件消息。那是我觉得困难并且觉得我应该质疑的部分。基本上得到一个状态机,将其重置为给定状态并运行它以拦截转换;一旦被拦截,新的目标状态就会被持久化到实体表中:

// stateMachineFactory is auto wired into the state handler
// repository is auto wired in the state handler

public void handleEvent(final Message<Event> message) {
  final Entity entity = message.getHeaders().get("ENTITY_HEADER", Entity.class);
  final State currentState = entity.getProcessState();
  StateMachine<State, Event> machine = stateMachineFactory.getStateMachine();

  machine.getStateMachineAccessor().doWithAllRegions(accessor -> accessor.resetStateMachine(
    new DefaultStateMachineContext<State, Event>(currentState, null, null, null, null)
  ));

  machine.getStateMachineAccessor().doWithAllRegions(accessor -> accessor.addStateMachineInterceptor(

    @Override
    public StateContext<State, Event> postTransition(final StateContext<State, Event> stateContext) {
      final Entity entity1 = stateContext.getMessage().getHeaders.get("ENTITY_HEADER", Entity.class);
      if (entity != null) {
        entity1.setState(stateContext.getTarget().getId());
        repository.save(entity1);
        return stateContext;
      }
      // if entity is null then throw exception
      ... omitted exception handling
    }

  );

  log.debug("Starting state machine to process [{}]", entity);
  stateMachine.start();
  stateMachine.sendEvent(message);
  stateMachine.stop();
}

为了完整起见,以下StateMachineConfig

@Override
public void configure(final StateMachineConfigurationConfigurer<State, Event> config) throws Exception {
  config.withConfiguration()
        .autoStartup(false);
}

@Override
public void configure(final StateMachineStateConfigurer<State, Event> sates) throws Exception {
  states.withStates()
        .initial(State.CREATED)
        .states(EnumSet.allOf(State.class));
}

@Override
public void configure(final StateMachineTransitionConfigurer<State, Event> transitions) throws Exception {
  transitions.withExternal()
             .source(State.CREATED)
             .target(State.ASSIGNED)
             .event(Event.ASSIGN_TO_AGENT)
           .and()
             .withExternal()
             .source(State.ASSIGNED)
             .target(State.IN_PROCESS)
             .event(Event.START_PROCESS);
}

我希望我能尽可能完整。如果需要任何说明,请告诉我。

我的问题是:是否有更好的设计来实现这个状态机,或者在这里可以看到一种合理的方法?

【问题讨论】:

    标签: spring-boot spring-statemachine


    【解决方案1】:

    我猜你的工作流绑定到“代理”——所以一个代理启动一个工作流,你想保持每个代理的工作流状态。现在我不知道代理是否可以启动多个工作流实例并并行处理它们,因此可能需要针对这些情况重新调整以下建议。

    直接的方法是为每个代理(如果可能有多个工作流实例,则为每个工作流)拥有一个 SM 实例。 当代理开始使用工作流时,您必须确定它是全新的工作流还是处于特定状态的现有工作流。

    如果是新的工作流程 - 在开始状态返回一个新的 SM 并发送所需的事件。

    如果它是现有的工作流,您需要创建一个 SM 并向 SM 提供当前工作流状态,并在 SM 初始化时进行必要的转换,然后再将其返回给服务调用者。状态应该预先保存到数据存储中。

    我不知道您的域,因此状态可以作为某些工作流实体或代理实体或其他东西的一部分保留 - 取决于应用上下文。

    关于谁在数据库中持久化状态有不同的方法。

    A) SM 可以对此负责(例如,在接收到事件时,SM 将从事件中提取必要的状态信息和上下文(例如 DB 实体 ID)并将其保存在 DB 中为该实体 ID,然后转换到下一个状态)。

    B)“编排”SM 的服务 XYZ 可以对此负责(例如,服务 XYZ 在另一个存储库服务上调用“持久”,如果操作成功,则服务 XYZ 将必要的事件发送到 SM - 然后 SM 只处理到下一个状态的转换)。

    【讨论】:

      猜你喜欢
      • 2015-08-16
      • 1970-01-01
      • 1970-01-01
      • 2015-07-21
      • 1970-01-01
      • 2021-10-17
      • 2011-01-09
      • 1970-01-01
      • 2019-03-31
      相关资源
      最近更新 更多