12 Ocak 2021 Salı

SpringBoot StateMachine Kullanımı

Giriş 
Şu satırı dahil ederiz
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.support.DefaultStateMachineContext;
Bu projeyi ilk olarak burada gördüm

Maven
Şu satırı dahil ederiz
<dependency>
   <groupId>org.springframework.statemachine</groupId>
   <artifactId>spring-statemachine-starter</artifactId>
</dependency>
1. Önce State'ler Enum olarak tanımlanır
2. Action'lar tanımlanır
3. Guard koşulları tanımlanır
3. EnumStateMachineConfigurerAdapter sınıfından kalıtarak Transition'lar, Guard'lar ve transition esnasında çalıştırılacak Action'lar takılır. Bu sınıfa @EnableStateMachineFactory anotasyonu eklenir.
4. StateMachineListener arayüzünden kalıtarak transition'lar esnasında bir şey çalıştırılabilir
5. StateMachine nesnesi Autowire edilir ve sendEvent() metodu kullanılarak event gönderilir.

Reactive StateMachine
Açıklaması şöyle
1. Spring State Machine version 3.0+ is fully reactive.

2. If the project is not based on the reactive stack, it makes sense to pay attention to version 2.5+, which is synchronous.

sendEvent method of the synchronous version returns true or false. But true is returned if the event is accepted, i.e. doesn’t violate the transition rules of the state machine. In this case, if an error occurred in the guard, action, or interceptor, then the method will still return true.

This is rather inconvenient if you want to include state transition in the current database transaction. To solve this problem, you can write wrappers that put the error into the state machine context.
Bu durumda StateMachineInterceptor kullanılır
Örnek
Şöyle yaparız
class StateMachineInterceptorWrapper<S, E>(
    private val interceptor: StateMachineInterceptor<S, E>,
) : StateMachineInterceptor<S, E> {

    ...
    
  override fun preEvent(message: Message<E>, stateMachine: StateMachine<S, E>): 
    Message<E> = wrap(stateMachine) {
    interceptor.preEvent(message, stateMachine)
  }
    
  private fun <T> wrap(stateMachine: StateMachine<S, E>, block: () -> T): T =
    try {
      block()
    } catch (t: Throwable) {
      stateMachine.extendedState.variables[STATE_MACHINE_ERROR_VARIABLE] = t
      stateMachine.setStateMachineError(RuntimeException(t))
      throw t
    }
}

fun <S, E> StateMachine<S, E>.sendEventOrThrow(event: E) {
  if (!sendEvent(event) || hasStateMachineError()) {
    val ex = extendedState.variables[STATE_MACHINE_ERROR_VARIABLE] as Throwable?
       ?: RuntimeException()
     throw ex
  }
}

Scheduled transitions
Açıklaması şöyle
SSM provides a simple mechanism for timed messages based on the ScheduledExecutorService
State persistence
Açıklaması şöyle
There are 2 ways to store a state.

1. You can use additional libraries such as Spring Statemachine Data Jpa to persist all state machines, their contexts, states' history, transitions, as well as guards and actions. Each entity will be stored in the corresponding database table.

2. If your project is already big enough, it is unlikely you want to refactor it significantly. Therefore, the second option is to keep persisting states as it is, but on each transition:

create an instance of the state machine
load the initial state
do the transition
update the state in the database

StateMachineFactory Sınıfı
Şu satırı dahil ederiz
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.support.DefaultStateMachineContext;
getStateMachine metodu
Örnek
Şöyle yaparız
PaymentStateChangeInterceptor paymentStateChangeInterceptor = ...;

StateMachine<PaymentState, PaymentEvent> build(Long paymentId){
  Payment payment = repository.getOne(paymentId);

  StateMachine<PaymentState, PaymentEvent> sm = stateMachineFactory
.getStateMachine(Long.toString(payment.getId()));

  sm.stop();

  sm.getStateMachineAccessor()
    .doWithAllRegions(sma -> {
      sma.addStateMachineInterceptor(paymentStateChangeInterceptor);
      sma.resetStateMachine(new DefaultStateMachineContext<>(payment.getState(),
null, null, null));
  });

  sm.start();

  return sm;
}
StateMachineInterceptorAdapter Sınıfı
preStateChange metodu
Örnek
Şöyle yaparız
import org.springframework.messaging.Message;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.support.StateMachineInterceptorAdapter;
import org.springframework.statemachine.transition.Transition;
import org.springframework.stereotype.Component;

@Component
public class PaymentStateChangeInterceptor extends
StateMachineInterceptorAdapter<PaymentState, PaymentEvent> { private final PaymentRepository paymentRepository; @Override public void preStateChange(State<PaymentState, PaymentEvent> state,
Message<PaymentEvent> message, Transition<PaymentState, PaymentEvent> transition,
StateMachine<PaymentState, PaymentEvent> stateMachine) { Optional.ofNullable(message).ifPresent(msg -> { Optional.ofNullable(Long.class.cast(msg.getHeaders()
.getOrDefault(PaymentServiceImpl.PAYMENT_ID_HEADER, -1L))) .ifPresent(paymentId -> { Payment payment = paymentRepository.getOne(paymentId); payment.setState(state.getId()); paymentRepository.save(payment); }); }); } }

Hiç yorum yok:

Yorum Gönder