Project

orchestr8

0.0
The project is in a healthy, maintained state
Define multi-step job workflows as directed acyclic graphs. Parallel execution, failure handling, data passing between steps, and a built-in DAG visualizer. No Redis — all state in your database.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 7.1
 Project Readme

Orchestr8

DAG-based job workflow orchestration for Rails. Define multi-step workflows as directed acyclic graphs with parallel execution, dependency tracking, failure handling, and a built-in dashboard. No Redis required — all state lives in your database.

CI

What It Does

  • Compose jobs into workflows with explicit dependencies
  • Steps with no unmet dependencies run in parallel automatically
  • Steps can pass output data to downstream steps
  • Configurable failure handling per step (halt the workflow or continue)
  • Built-in web dashboard with DAG visualization and real-time status updates
  • Works with any Active Job backend (Solid Queue, Sidekiq, GoodJob, etc.)
  • Test helper for synchronous, in-process workflow execution in specs

Installation

Add to your Gemfile:

gem "orchestr8"

Run the migrations:

bundle exec rails orchestr8:install:migrations
bundle exec rails db:migrate

Mount the dashboard in config/routes.rb:

mount Orchestr8::Engine => "/orchestr8"

Quick Start

1. Define a Workflow

# app/workflows/etl_workflow.rb
class EtlWorkflow < Orchestr8::Workflow
  step :fetch,     job: FetchDataJob
  step :transform, job: TransformJob, after: :fetch
  step :load,      job: LoadJob,      after: :transform
end

2. Add Stepable to Your Jobs

class FetchDataJob < ApplicationJob
  include Orchestr8::Stepable

  def perform(arguments)
    data = fetch_data(arguments["url"])
    output(data)   # share data with downstream steps
  end
end

class TransformJob < ApplicationJob
  include Orchestr8::Stepable

  def perform(arguments)
    raw = payloads[:fetch]   # read upstream output
    output(transform(raw))
  end
end

3. Run a Workflow

EtlWorkflow.create!(arguments: { "url" => "https://api.example.com/data" })

Orchestr8 enqueues ready steps immediately, then re-evaluates and enqueues downstream steps as each step completes.

Step Options

Option Type Default Description
job: Class required The Active Job class to perform
after: Symbol or Array [] Step name(s) this step depends on
on_failure: :halt / :continue :halt What to do when this step fails
queue: String nil Override the queue name for this step
class MyWorkflow < Orchestr8::Workflow
  step :fetch,   job: FetchJob,   queue: "critical"
  step :notify,  job: NotifyJob,  after: :fetch, on_failure: :continue
  step :cleanup, job: CleanupJob, after: %i[fetch notify]
end

Failure Handling

By default (on_failure: :halt), a failed step prevents downstream steps from running and marks the workflow as failed.

With on_failure: :continue, downstream steps still run even if this step fails. The failed step's output will be nil in payloads.

class ReportWorkflow < Orchestr8::Workflow
  step :fetch_primary,   job: FetchPrimaryJob
  step :fetch_secondary, job: FetchSecondaryJob, on_failure: :continue
  step :aggregate,       job: AggregateJob,       after: %i[fetch_primary fetch_secondary]
end

Retry

Retry a single failed step:

step = workflow.steps.find_by(name: "transform")
step.retry!

Retry all failed steps in a workflow:

workflow.retry!

Both reset the step(s) to pending and re-trigger the scheduler immediately.

Dashboard

Mount the engine in your routes:

mount Orchestr8::Engine => "/orchestr8"

The dashboard at /orchestr8 lists all workflows with status and step progress. Each workflow's detail page shows a DAG visualization with real-time status updates via Turbo Streams.

Securing the Dashboard

Configure a base controller class that enforces authentication:

# config/initializers/orchestr8.rb
Orchestr8.configure do |config|
  config.base_controller_class = "AdminController"
end

The engine's controllers will inherit from that class, so any before_action defined there (e.g. authenticate_admin!) will apply automatically.

Testing

Use Orchestr8::TestHelper to execute workflows synchronously in specs without a real job backend:

# spec/rails_helper.rb (or in a specific spec)
require "orchestr8/test_helper"

RSpec.describe "My feature" do
  include Orchestr8::TestHelper

  it "completes the workflow inline" do
    workflow = MyWorkflow.create!(arguments: { "key" => "value" })

    expect(workflow.reload.status).to eq("completed")
    expect(workflow.steps.all? { |s| s.status == "completed" }).to be true
  end
end

Orchestr8::TestHelper hooks into RSpec's before/after to enable and reset synchronous mode around each example. In synchronous mode the scheduler runs each job inline via perform_now and loops until all reachable steps are processed.

Works With

Orchestr8 uses Active Job and makes no assumptions about the backend.

Backend Gem Notes
Solid Queue solid_queue Recommended for zero-Redis setups
Sidekiq sidekiq Set config.active_job.queue_adapter = :sidekiq
GoodJob good_job Postgres-native, supports concurrency controls

License

MIT — see LICENSE.