We are working on a project where we need to support stateful multi-step UI flows. We want backend to keep track of users progress and gather data from user input. Sometimes these flows will fork.
We found 2 solutions that might work:
Reactor - with halting and pausing the workflow
State machine
One of the requirements is that the flow must fork sometimes, i.e. if a specific condition is met a different set of screens must be presented.
Reactors can kind of be saved, but its something that we plan on adding real support for in the future. A reactor can be “halted” by a step, which produces the reactor state. AFAIK it’s not realistically/easily be serializable to a database but @jimsynz could correct me if I’m wrong.
My suggestion is to leverage ash_state_machine. It is perfectly capable of modeling forking logic.
transitions do
# possible state transitions can overlap, etc.
transition :update_delivery_status,
from: :en_route, to: [:delivered, :redelivering, :lost]
transition :redelivery_attempted,
from: :redelivering, to: [:redelivering, :lost]
end
actions do
update :update_delivery_status do
# whatever inputs you want
argument :delivery_event, :atom, constraints: [one_of: [....]]
change fn changeset, _ ->
# dynamically/arbitrarily determine new states
if ... do
AshStateMachine.transition_state(changeset, :new_state)
else
AshStateMachine.transition_state(changeset, :new_state)
end
end
# or do it declaratively
change transition_state(:delivered), where: [argument_equals(:delivery_event, :success)]
change transition_state(:lost), where: [argument_equals(:delivery_event, :failed_to_find_package)]
...
end
end