The Business Logic
Framework for Ruby

Build maintainable systems, not just scripts.

See it in Action

From simple tasks to complex workflows, see how CMDx brings clarity to your business logic.

Define a Task

Declare typed attributes with validation, coercion, and defaults. Write your business logic in a single work method with built-in flow control for success, failure, and skip states.

class ApproveLoan < CMDx::Task
  on_success :notify_applicant!

  required :application_id, type: :integer
  optional :override_checks, default: false

  def work
    if application.nil?
      fail!("Application not found", code: 404)
    elsif application.approved?
      skip!("Already approved")
    else
      application.approve!
      context.approved_at = Time.current
    end
  end

  private

  def application
    @application ||= LoanApplication.find_by(id: application_id)
  end

  def notify_applicant!
    ApprovalMailer.approved(application).deliver_later
  end
end

Execute & React

Every execution returns a standardized Result object. No more rescuing exceptions for control flow. Check the outcome and access returned values, error messages, and metadata.

# Execute with typed, validated arguments
result = ApproveLoan.execute(application_id: 42)

# React to the outcome
if result.success?
  puts "Approved at #{result.context.approved_at}"

elsif result.skipped?
  puts "Skipped: #{result.reason}"

elsif result.failed?
  puts "Failed: #{result.reason}"
  puts "Code: #{result.metadata[:code]}"
end

# Bang variant raises on failure
result = ApproveLoan.execute!(application_id: 42)
# => raises CMDx::ExecutionError on failure

Compose Workflows

Chain tasks into declarative pipelines with shared context, conditional execution, and configurable halt behavior. Run tasks in parallel for maximum throughput.

class OnboardCustomer < CMDx::Task
  include CMDx::Workflow

  # Sequential pipeline
  task CreateAccount
  task SetupBilling
  task AssignPlan

  # Parallel notifications
  tasks SendWelcomeEmail,
        SendWelcomeSms,
        CreateDashboard,
        strategy: :parallel

  # Conditional execution
  task ScheduleOnboardingCall,
       if: :premium_plan?

  private

  def premium_plan?
    context.plan.tier == :premium
  end
end

# Run the entire workflow
result = OnboardCustomer.execute(email: "jane@co.com")

Fault Tolerance

Handle transient failures automatically with configurable retries, selective exception matching, and exponential backoff. No manual intervention needed.

class ProcessPayment < CMDx::Task
  settings retries: 5,
           retry_on: [Stripe::RateLimitError,
                      Net::ReadTimeout],
           retry_jitter: :exponential_backoff

  required :payment_id, type: :integer

  def work
    payment = Payment.find(payment_id)
    context.charge = Stripe::Charge.create(
      amount: payment.amount_cents,
      currency: "usd",
      source: payment.token
    )
  end

  private

  def exponential_backoff(current_retry)
    2 ** current_retry # 2s, 4s, 8s, 16s, 32s
  end
end

result = ProcessPayment.execute(payment_id: 99)
result.retried?  # => true
result.retries   # => 2

Observe Everything

Every execution automatically generates structured logs with chain IDs, runtime metrics, and contextual metadata. Trace complex workflows and debug issues with ease.

# Structured logs are generated automatically
# with correlation IDs for full traceability

I, [2026-02-06T14:22:01.000 #4891] INFO -- CMDx:
index=0 chain_id="018c..e5"
type="Task" class="CreateAccount"
state="complete" status="success"
metadata={runtime: 12}

I, [2026-02-06T14:22:01.050 #4891] INFO -- CMDx:
index=1 chain_id="018c..e5"
type="Task" class="SetupBilling"
state="complete" status="success"
metadata={runtime: 45}

W, [2026-02-06T14:22:01.200 #4891] WARN -- CMDx:
index=2 chain_id="018c..e5"
type="Task" class="SendWelcomeEmail"
state="complete" status="skipped"
metadata={runtime: 2, reason: "Email not configured"}

I, [2026-02-06T14:22:01.250 #4891] INFO -- CMDx:
index=0 chain_id="018c..e5"
type="Workflow" class="OnboardCustomer"
state="complete" status="success"
metadata={runtime: 250}

Why CMDx?

Designed for developers who value clarity, consistency, and reliability.

⚡️

Zero Dependencies

Lightweight and pure Ruby. Drop it into Rails, Sinatra, Hanami, or any plain Ruby script without bloating your Gemfile.

🛡️

Type Safety

Declare inputs with strict typing, coercion, and validation. Catch bad data at the boundary, before it corrupts your logic.

🔍

Observability

Built-in tracing, structured logging, and error tracking. Know exactly what happened, when, and why.

🧩

Composability

Chain small, focused commands into complex workflows. Reuse logic across your application with confidence.

Predictable Results

No more rescuing exceptions for control flow. Every command returns a standardized Result object.

🌍

Production Ready

Includes I18n, retries, deprecation handling, and middleware support out of the box.

The CERO Pattern

A simple mental model for reliable logic.

🏗️
Compose

Build reusable, single-responsibility tasks with typed attributes and validation.

⚡️
Execute

Invoke tasks with a consistent API that handles errors and logging automatically.

🎯
React

Handle standardized result objects with clear success, failure, or skipped states.

📊
Observe

Trace execution with structured logs, correlation IDs, and runtime metrics.

How We Compare

CMDx delivers the full stack without the bloat.

Feature CMDx Actor Interactor ActiveInteraction LightService
Zero Dependencies
Typed Attributes
Type Coercion
Built-in Logging
Middleware System
Fault Tolerance
100% Pure Ruby
0 Dependencies
Type Safe Inputs

Built For Real World Logic

From simple scripts to complex enterprise workflows.

🏦 Financial Operations

Handle payments, loans, and ledgers with audit trails and transactional integrity.

🔄 Data Pipelines

Structure ETL processes with clear steps, error handling, and resumption capabilities.

🛒 E-commerce

Orchestrate order fulfillment, inventory checks, and shipping logistics reliably.