Building multi-step, forking flows in Ash

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.

Questions at hand:

  • can a paused Reactor workflow be saved in the DB?
  • is Reactor a good fit for the use case?
  • are there any other better tools for the job?
1 Like

:wave: 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
1 Like