Skip to content

Instantly share code, notes, and snippets.

@ChristianAlexander
Created July 2, 2025 23:05
Show Gist options
  • Save ChristianAlexander/a83a7189c1509939ac3b7b8812b1b0f2 to your computer and use it in GitHub Desktop.
Save ChristianAlexander/a83a7189c1509939ac3b7b8812b1b0f2 to your computer and use it in GitHub Desktop.
Reactor Demo

Reactor Demo

Mix.install([
  {:reactor, "~> 0.15.5"},
  {:kino, "~> 0.16.0"},
  {:nanoid, "~> 2.1"}
])

Demo Services

defmodule HotelService do
  require Logger

  @failure_rate 0.2

  def book() do
    Logger.info("Booking hotel")
    
    if :rand.uniform() < @failure_rate do
      Logger.error("[#{__MODULE__}] failed to reserve hotel room")
      {:error, "Failed to reserve room"}
    else
      {:ok, confirmation_code()}
    end
  end

  def cancel(code) do
    Logger.warning("[#{__MODULE__}] canceling hotel reservation #{code}")
    :ok
  end

  defp confirmation_code() do
    "HOTEL-#{Nanoid.generate()}"
  end
end
defmodule FlightService do
  require Logger

  @failure_rate 0.05

  def find_flight_number() do
    Logger.info("Finding flight")

    if :rand.uniform() < @failure_rate do
      Logger.error("[#{__MODULE__}] failed to reserve flight")
      {:error, "Failed to reserve flight"}
    else
      {:ok, "CS-2025"}
    end
  end

  def book(flight_number) do
    Logger.info("Booking flight, #{flight_number}")

    if :rand.uniform() < @failure_rate do
      Logger.error("[#{__MODULE__}] failed to reserve flight")
      {:error, "Failed to reserve flight"}
    else
      {:ok, confirmation_code()}
    end
  end

  def cancel(code) do
    Logger.warning("[#{__MODULE__}] canceling flight reservation #{code}")
    :ok
  end

  defp confirmation_code() do
    "FLY-#{Nanoid.generate()}"
  end
end
defmodule EmailService do
  require Logger

  @timeout_rate 0.5

  def send(destination, message) do
    Logger.info("Sending email to #{destination}: #{message}")

    cond do
      :rand.uniform() < @timeout_rate ->
        :timer.sleep(1_000)

        Logger.warning("[#{__MODULE__}] timeout")
        {:error, :timeout}

      String.contains?(destination, "example.net") ->
        Logger.error("[#{__MODULE__}] bad destination address")
        {:error, :bad_destination}

      true ->
        {:ok, :sent}
    end
  end
end

Happy Path

{:ok, flight_number} = FlightService.find_flight_number()
{:ok, flight_confirmation} = FlightService.book(flight_number)
{:ok, hotel_confirmation} = HotelService.book()

{:ok, :sent} = EmailService.send("[email protected]", """
Your hotel and flight have been booked!
Flight confirmation: #{flight_confirmation}
Hotel confirmation: #{hotel_confirmation}
""")

Trip Reactor

defmodule SimpleTripReactor do
  use Reactor

  input(:user_email)

  step :find_flight_number do
    run(fn _ -> FlightService.find_flight_number() end)
  end

  step :book_hotel do
    run(fn _ ->
      HotelService.book()
    end)
  end

  step :book_flight do
    argument(:flight_number, result(:find_flight_number))

    run(fn %{flight_number: flight_number} ->
      FlightService.book(flight_number)
    end)
  end

  step :email_user do
    argument(:user_email, input(:user_email))
    argument(:hotel_code, result(:book_hotel))
    argument(:flight_code, result(:book_flight))

    run(fn inputs ->
      message = """
      Your hotel and flight have been booked!
      Flight confirmation: #{inputs.flight_code}
      Hotel confirmation: #{inputs.hotel_code}
      """

      EmailService.send(inputs.user_email, message)
    end)
  end

  collect :booking_results do
    argument(:hotel_code, result(:book_hotel))
    argument(:flight_number, result(:find_flight_number))
    argument(:flight_code, result(:book_flight))

    wait_for(:email_user)
  end

  return(:booking_results)
end
Reactor.Mermaid.to_mermaid!(SimpleTripReactor, output: :binary)
|> Kino.Mermaid.new()
Reactor.run(SimpleTripReactor, %{user_email: "[email protected]"})

Reactor Step Modules

defmodule BookFlightStep do
  use Reactor.Step

  @impl Reactor.Step
  def run(%{flight_number: flight_number}, _context, _options) do
    FlightService.book(flight_number)
  end

  @impl Reactor.Step
  def undo(confirmation_code, _arguments, _context, _options) do
    FlightService.cancel(confirmation_code)
  end
end

Advanced Trip Reactor

defmodule TripReactor do
  use Reactor

  input(:user_email)

  step :find_flight_number do
    run(fn _ -> FlightService.find_flight_number() end)
  end

  step :book_hotel do
    run(fn _ -> HotelService.book() end)
    undo(fn confirmation -> HotelService.cancel(confirmation) end)
  end

  step :book_flight, BookFlightStep do
    argument(:flight_number, result(:find_flight_number))
  end

  step :email_user do
    argument(:user_email, input(:user_email))
    argument(:hotel_code, result(:book_hotel))
    argument(:flight_code, result(:book_flight))
    max_retries(2)

    run(fn inputs ->
      message = """
      Your hotel and flight have been booked!
      Flight confirmation: #{inputs.flight_code}
      Hotel confirmation: #{inputs.hotel_code}
      """

      EmailService.send(inputs.user_email, message)
    end)

    compensate(fn
      :bad_destination -> :ok
      :timeout -> :retry
    end)
  end

  collect :booking_results do
    argument(:hotel_code, result(:book_hotel))
    argument(:flight_number, result(:find_flight_number))
    argument(:flight_code, result(:book_flight))

    wait_for(:email_user)
  end

  return(:booking_results)
end
Reactor.Mermaid.to_mermaid!(TripReactor, output: :binary)
|> Kino.Mermaid.new()
Reactor.run(TripReactor, %{user_email: "[email protected]"})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment