spring-projects / spring-statemachine

Spring Statemachine is a framework for application developers to use state machine concepts with Spring.
1.56k stars 613 forks source link

Support StateMachineClient concept #781

Open jvalkeal opened 5 years ago

jvalkeal commented 5 years ago

As with a new reactive interfaces a communication layer is a bit more complicated than just calling a method to send an event as it used to be with old blocking layer. I think it would be beneficial for a new StateMachineClient to be able to send events in a reactive way and wait some things to happen, etc.

For example in tests we do something like:

    public static <T> Mono<Message<T>> eventAsMono(T event) {
        return Mono.just(MessageBuilder.withPayload(event).build());
    }

    public static <T> Mono<Message<T>> eventAsMono(Message<T> event) {
        return Mono.just(event);
    }

    @SafeVarargs
    public static <T> Flux<Message<T>> eventsAsFlux(T... events) {
        return Flux.fromArray(events).map(e -> MessageBuilder.withPayload(e).build());
    }

    public static <S, E> void doSendEventAndConsumeAll(StateMachine<S, E> stateMachine, E event) {
        StepVerifier.create(stateMachine.sendEvent(eventAsMono(event)))
            .thenConsumeWhile(eventResult -> true)
            .verifyComplete();
    }

    public static <S, E> void doSendEventAndConsumeAll(StateMachine<S, E> stateMachine, Message<E> event) {
        StepVerifier.create(stateMachine.sendEvent(eventAsMono(event)))
            .thenConsumeWhile(eventResult -> true)
            .verifyComplete();
    }

For a simple cases like just wanting to send an event we could just consume returned flux and then provide more higher level features like waiting a particular state, etc. Obviously StepVerifier is a reactor testing utility so we would not use it but provide our own client layer.

All this is just an idea for now and will probably evolve from initial prototyping into a fully usable client.

jvalkeal commented 5 years ago

As in so my cases these ideas come out from trying to make documentation look sane for a user. For example a first example of sending an event to a machine in a docs is something like:

@Autowired
private StateMachine<States, Events> stateMachine;

@Override
public void run(String... args) throws Exception {
    stateMachine.sendEvent(Events.E1);
    stateMachine.sendEvent(Events.E2);
}

Internally we've changed sendEvent to block so that user would not need to immediately change their own code. Due to deprecation these methods will eventually go away, thus the idea of a StateMachineClient.

First sample which changed to reactive methods then have something like:

@PostMapping("/events")
public Flux<EventResult> events(@RequestBody Flux<EventData> eventData) {
    return eventData
        .filter(ed -> ed.getEvent() != null)
        .map(ed -> MessageBuilder.withPayload(ed.getEvent()).build())
        .flatMap(m -> stateMachine.sendEvent(Mono.just(m)))
        .map(EventResult::new);
}

Essentially StateMachineClient would be to find common cases sending events and doing something with a consumed flux.

@Autowired
private StateMachineClient<States, Events> stateMachineClient;

@Override
public void run(String... args) throws Exception {
    stateMachineClient.sendEventsAndSubscribe(Events.E1, Events.E2);
}

Also you're getting flux back from a machine telling if event is denied, accepted or deferred so there's a lot of combinations what this StateMachineClient could actually do with all possible other combinations what's actually happening.

There's really now back magic here in this client, just to prevent user writing boilerplate code over and over again.

mroeppis commented 1 year ago

Is there any progress on this?

I'm currently migrating Spring Statemachine from 2.x to 3.x wondering how I should deal with callers that expect a simple boolean value after sending an event. At the moment I don't want to make my application reactive all the way down. So, it would be nice to have an equivalent method Mono<Boolean> StateMachineClient#sendEventAndGetIsAccepted( Event E) telling me simply whether it was accepted or not.