惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 司徒正美
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Last Week in AI
Last Week in AI
大猫的无限游戏
大猫的无限游戏
博客园 - Franky
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
爱范儿
爱范儿
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
博客园 - 叶小钗
博客园_首页
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
V
V2EX
V
Visual Studio Blog
博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
量子位
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
美团技术团队

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Ruby Reactor Now Has Middlewares and OpenTelemetry — Here...
Artur Pañach · 2026-06-17 · via DEV Community

You've built a checkout reactor that reserves inventory, charges a card, generates a shipping label, and sends a confirmation email. It runs through Sidekiq. When something fails, compensation logic rolls it back. It works.

Then your team asks: "How many checkouts failed this week? Which step? How long does the charge step take at p99? Can we see a trace through the entire system?"

Before v0.5.0, you'd need to add logging calls to every step, build a custom Sidekiq middleware, and figure out how to correlate traces across async job boundaries. Now it's one line of config.

Enter Middlewares

Ruby Reactor 0.5.0 introduces a middleware pipeline — the same pattern that powers Rack, but designed for saga execution. A middleware is a plain Ruby object that hooks into the reactor lifecycle:

class TimingMiddleware < RubyReactor::Middleware
  def initialize(**options)
    super
    @started = {}
  end

  def on_start_step(step_name, _arguments, _context)
    @started[step_name] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  end

  def on_complete_step(step_name, _result, _context)
    started = @started.delete(step_name)
    return unless started
    elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
    logger.info("step #{step_name} took #{elapsed.round(4)}s")
  end
end

This middleware times every step. Register it globally:

RubyReactor.configure do |config|
  config.middlewares = [TimingMiddleware]
end

Now every reactor — every checkout, every refund, every data import — gets step-level timing, for free.

The full lifecycle (20+ events)

Middlewares can observe the complete execution lifecycle:

Phase Events
Reactor on_start_reactor, on_complete_reactor, on_failed_reactor
Step on_start_step, on_complete_step, on_failed_step, on_retry_attempt
Compensation on_start_compensation, on_complete_compensation, on_failed_compensation
Undo on_start_undo, on_complete_undo, on_failed_undo
Coordination on_lock_acquired, on_lock_failed, on_semaphore_acquired, …
Async on_before_async_enqueue

You can build custom logging, metrics, audit trails, and alerting — all without touching your reactor code.

Why this matters

  • Separation of concerns — business logic lives in steps, observability lives in middlewares
  • Composability — stack multiple middlewares (timing + logging + alerting) like Lego
  • Safety — middleware errors are caught and logged, never crash your reactor
  • Per-reactor override — declare middleware AuditMiddleware, level: :info on specific reactors

OpenTelemetry: Distributed Tracing, Zero Config

The most powerful middleware ships built-in: RubyReactor::OpenTelemetry.

One line of configuration:

RubyReactor.configure do |config|
  config.middlewares = [RubyReactor::OpenTelemetry]
end

That's it. Every reactor run becomes a full OpenTelemetry trace:

CheckoutReactor (span)
├── step.reserve_inventory (span)
├── step.charge_card (span)
│   └── step.charge_card.enqueue (span) ← async hand-off
├── step.generate_label (span)
└── step.send_confirmation (span)

If a step fails and compensation runs:

├── step.charge_card (span, ERROR)
├── compensate.charge_card (span)
└── undo.reserve_inventory (span)

Async boundaries? Handled.

When a reactor hands work to a Sidekiq worker — an async step, a retry, or a map element — the middleware automatically injects the trace context into the serialized payload. The worker picks it up and continues the trace. The result is a single, connected trace across processes.

Sensitive data? Redacted.

class LoginReactor < RubyReactor::Reactor
  input :email
  input :password, redact: true  # shows as [REDACTED] in traces
end

No secrets leaking into your observability platform.

Any exporter works

Ruby Reactor produces OpenTelemetry spans. Where they go is up to your OTel SDK configuration:

# Datadog, Honeycomb, Jaeger, Grafana — pick your exporter
OpenTelemetry::SDK.configure do |c|
  c.service_name = "checkout_service"
  c.add_span_processor(
    OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
      OpenTelemetry::Exporter::OTLP::Exporter.new
    )
  )
end

What else is new in the 0.5.x line

v0.5.1 — Enhanced Validations
The input DSL got streamlined. Cleaner syntax for dry-validation schemas, better error messages when validation fails.

v0.5.2 — Nonce Lock
with_ordered_lock assigns a monotonically increasing nonce to each enqueued execution. Guarantees exactly-once processing with strict FIFO ordering — perfect for ledgers and payment processing where order matters.

Enhanced Testing
New RSpec matchers for coordination primitives: be_skipped, be_locked, have_ordered_lock_next, have_ordered_lock_in_flight, be_ordered_lock_drained. Plus sidekiq_helpers and storage_reset utilities.

Why this matters for production Ruby apps

Observability is no longer optional. If you're running business workflows in production, you need to know:

  • Which steps are slow? (timing middleware)
  • What's failing and why? (tracing + error events)
  • Is the system healthy? (metrics middleware)
  • Who changed what and when? (audit middleware)

Ruby Reactor 0.5.x gives you this without pulling in external workflow engines, without custom Sidekiq middleware, and without scattering logging calls through your business logic.


Gem: gem 'ruby_reactor', '~> 0.5'
Repo: github.com/arturictus/ruby_reactor
Middleware docs: documentation/middlewares.md

If you've been putting off observability for your Sidekiq workflows, there's no longer an excuse. ⭐ the repo and give it a try.