Your First Workflow

Copy Markdown View Source

Continuum workflows are ordinary Elixir modules that use Continuum.Workflow. The workflow function is pure orchestration code: it may call activities, wait for signals, set timers, and use Continuum's deterministic primitives.

defmodule MyApp.OrderFlow do
  use Continuum.Workflow, version: 1

  def run(%{order_id: order_id, items: items}) do
    {:ok, validated} = activity MyApp.Activities.ValidateOrder.run(%{items: items})
    {:ok, charge} = activity MyApp.Activities.ChargeCard.run(%{order_id: order_id})

    case await signal(:fraud_review) do
      :approved -> activity MyApp.Activities.ShipOrder.run(%{order_id: order_id})
      :rejected -> {:error, {:rejected, charge}}
    end

    {:ok, validated}
  end
end

Start the run from application code:

{:ok, run_id} =
  Continuum.start(MyApp.OrderFlow, %{
    order_id: "order_123",
    items: [%{sku: "sku_1", qty: 1}]
  })

Send a signal when outside input arrives:

:ok = Continuum.signal(run_id, :fraud_review, :approved)

Wait for completion in tests or synchronous scripts:

{:ok, %{state: :completed, result: result}} = Continuum.await(run_id, 5_000)

For Postgres-backed execution, configure a repo and generate the schema:

config :continuum,
  repo: MyApp.Repo,
  journal: Continuum.Runtime.Journal.Postgres
mix continuum.gen.migration --repo MyApp.Repo
mix ecto.migrate

Add Continuum's runtime children after your repo in your application's supervision tree:

children =
  [
    MyApp.Repo,
    {Phoenix.PubSub, name: MyApp.PubSub}
  ] ++ Continuum.children()

The engine persists every effect in continuum_events. If the BEAM process dies, the dispatcher re-leases suspended work and replays the history through the same workflow module.