qmuntal / stateless

Go library for creating finite state machines
https://wikipedia.org/wiki/Finite-state_machine
BSD 2-Clause "Simplified" License
974 stars 53 forks source link

Allow setting a state machine to some intermediate state #72

Open husam-e opened 9 months ago

husam-e commented 9 months ago

In scenarios where state machine entities are persisted and outlive a host's memory, and then are later retrieved to act upon, it would be helpful to be able to efficiently load the state machine logic for its current state, to know what it can do and fire off tasks and potentially transition to the next state.

So something like func (sm *StateMachine[S, T]) SetState(state S), to immediately put the StateMachine into the state the entity is in, and act on it from there.

Is there appetite for something like this, and are there any concerns or limitations preventing it from being added?

qmuntal commented 9 months ago

Hi @husam-e. Thanks for submitting this proposal.

What you are asking is already possible using a state machine with external storage:

var state State = "A"
sm := stateless.NewStateMachineWithExternalStorage(func(_ context.Context) (State, error) {
    return state, nil
}, func(_ context.Context, s State) error {
    state = s
    return nil
}, stateless.FiringQueued)

...

state = "B"

Does it work for you?

husam-e commented 9 months ago

Thanks @qmuntal ! I see that can work for one particular entity at a time, where the stateAccessor delegates to a DB fetch or some object that reflects the persisted state. However my thinking is to use the constructed state machine in a more "functional" way, so that I can reuse the same instance as a blueprint for many different entities at any point in time, that can all be in different states.

So thinking of constructing the state machine definition once, holding that in memory, then when handling requests for certain entities in a specific state, identifying what is possible at that state from the state machine defined and then executing those triggers/tasks. And then doing the same for another entity that could be in a different state.

With the current implementation, I think I'd have to reconstruct the state machine for each entity I operate on (so every time I want to do something essentially), and tie the stateAccessor to wherever that particular entity is stored. There may be some performance consequences to that?

(Hope this makes sense!)