Сохранять информацию о состоянии с помощью StateMachine в Spring Boot - PullRequest
1 голос
/ 30 сентября 2019

Я работаю над проектом, в котором мы используем конечный автомат для реализации рабочего процесса. У меня возникают некоторые проблемы с нагреванием того, что было сделано, и я хотел бы посмотреть, может ли быть лучший дизайн / реализация для моей проблемы.
Я постараюсь показать, что у нас есть на данный момент.

Пожалуйста, игнорируйте process_agent на данный момент, я хотел бы сосредоточиться на process_state только для начала. Я просто хочу создать процесс, и конечный автомат должен немедленно перейти из CREATED в ASSIGNED и сохранить это состояние в таблице Entity (по умолчанию я просто установил бы текущего пользователя в качестве агента на данный момент).

Существует таблица Entity с двумя данными: process_agent и process_state
На данный момент существует только три State s, определяемые как Enums: CREATED, ASSIGNED и IN_PROCESS
В настоящий момент существует только два Event с, определяемых как Enums: ASSIGN_TO_AGENT и START_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);
}

Я надеюсь, что смогу быть настолько полным, насколько это возможно,Пожалуйста, дайте мне знать, если нужны какие-либо разъяснения.

Мой вопрос: есть ли лучший дизайн для реализации этого конечного автомата, или здесь можно увидеть разумный подход?

1 Ответ

0 голосов
/ 01 октября 2019

Я бы предположил, что ваш рабочий процесс связан с «агентом», поэтому агент запускает рабочий процесс, и вы хотите сохранить состояние рабочего процесса для каждого агента. Теперь я не знаю, может ли агент запустить несколько экземпляров рабочего процесса и обрабатывать их параллельно, поэтому, возможно, потребуется скорректировать приведенное ниже предложение для этих случаев.

Прямой подход заключается в том, чтобы иметьЭкземпляр SM для агента (и для каждого рабочего процесса, если возможно несколько экземпляров рабочего процесса). Когда агент начинает работать с рабочим процессом, вы должны определить, является ли он совершенно новым рабочим процессом или уже существует в определенном состоянии.

, если это новый рабочий процесс - вернуть новый SM в начальное состояние и отправитьтребуемое событие.

если это существующий рабочий процесс, необходимо создать SM и передать SM текущее состояние рабочего процесса и выполнить необходимые переходы при инициализации SM, прежде чем возвращать его вызывающей стороне службы. Состояние должно быть предварительно сохранено в хранилище данных.

Я не знаю ваш домен, поэтому состояние может сохраняться как часть некоторого объекта Workflow или объекта Agent или чего-то еще - зависит от контекста приложения.

Существуют разные подходы к тому, кто сохраняет состояние в БД.

A) SM может быть ответственным за это (например, после получения события SM извлечетнеобходимую информацию о состоянии и контекст (например, идентификатор объекта БД) из события и сохранить его в БД для этого идентификатора объекта, а затем перейти к следующему состоянию).

B) СлужбаXYZ, который «координирует» SM, может быть ответственным за это (например, Service XYZ вызывает «persist» для другой службы репозитория, и если это успешная операция, то Service XYZ отправляет необходимое событие SM - тогда только SMобрабатывает переход в следующее состояние).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...