Skip to content

Tutorials

CMDx Patterns: Debugging and Observability

Part 4 of the CMDx Patterns series

Targets CMDx v1.21.

It's 2 AM. Your pager fires. A customer reports that their order went through but they never got a confirmation email. You open your log aggregator and search for the user ID. In a typical Rails app, you'd find a scattered trail of puts-style logs, maybe a Sentry exception if you're lucky, and no clear picture of what actually happened.

With CMDx, you search for the chain_id and see every task that ran, in order, with timing, status, and metadata. The confirmation email task shows status: "skipped", reason: "User unsubscribed from order notifications". Mystery solved in under a minute.

That's the power of observability built into the framework. This post covers how to use CMDx's logging, chain correlation, result inspection, and tagging to debug problems fast—in both development and production.

CMDx Patterns: The Error Handling Playbook

Part 3 of the CMDx Patterns series

Targets CMDx v1.21.

I used to think error handling was simple. Something goes wrong, you rescue it, done. Then I started building systems where "something went wrong" had fifteen different flavors—each requiring a different response. A missing record is not the same as a network timeout. A user's expired subscription is not the same as a billing system outage. Treating them identically is how you end up with generic "Something went wrong" error pages and support tickets that take hours to triage.

CMDx gives you four distinct mechanisms for handling problems: skip!, fail!, throw!, and letting exceptions propagate. Knowing which one to reach for—and when—is the difference between a system that degrades gracefully and one that falls over at the first sign of trouble.

CMDx Patterns: Advanced Middleware Stacks

Part 2 of the CMDx Patterns series

Targets CMDx v1.21.

Middleware is one of those features that's easy to understand and hard to use well. You write a simple wrapper, register it, and it works. Then you write another. And another. Before long, you've got six middlewares on every task, they're firing in an order you didn't intend, and you're spending more time debugging the middleware stack than the business logic it wraps.

I've been through that cycle enough times to develop opinions about how to compose middleware stacks in CMDx. This post covers the patterns that survived contact with production Ruby applications—from simple wrappers to sophisticated multi-layer stacks.

CMDx Patterns: Defensive Contracts

Part 1 of the CMDx Patterns series

Targets CMDx v1.21.

I have a rule when building Ruby tasks: if a task can be misused, it will be misused. Not out of malice—out of haste, incomplete documentation, or the natural entropy of a growing codebase. Someone passes a string where you expected an integer. Someone forgets to read the context key you set. Someone calls your task from a new workflow and the whole pipeline falls over because the inputs were subtly wrong.

Defensive contracts are CMDx's answer to this. By combining required/optional attributes, validations, coercions, and returns, you build tasks that are impossible to misuse silently. Bad data fails loudly at the boundary. Missing outputs fail immediately at the source. The contract is the code, and the code enforces itself.

Returns and Contracts: Making Your Tasks Predictable

Targets CMDx v1.20.

I used to have a recurring nightmare. Not the falling kind—the kind where I'm staring at a service object and trying to figure out what it puts into the context. The work method sets context.user on line 12, context.token on line 28, but only if the conditional on line 15 passes. Oh, and there's a context.session_id that gets set inside a private method three screens down.

Every consumer of that task is making an implicit assumption about what the context will contain after execution. When those assumptions break, the error shows up somewhere else entirely—a NoMethodError in a downstream task, a nil where a mailer expected a user object.

That's why I built returns into CMDx. It makes the output contract explicit, enforced, and impossible to forget.

Structuring Large CMDx Codebases

Targets CMDx v1.20.

Your first CMDx task is easy. Your tenth is manageable. But what about your hundredth? I've seen projects where the app/tasks/ directory becomes a dumping ground—flat files with no organization, inconsistent naming, duplicated middleware registrations, and base classes that try to do everything.

Scaling a CMDx codebase isn't about the framework. It's about the conventions you establish early and enforce consistently. This post is the playbook I wish I had when my first CMDx project grew from 10 tasks to 200.

CMDx as a Pragmatic Alternative to Event Sourcing

Targets CMDx v1.20.

Event Sourcing is one of those ideas that sounds perfect in a conference talk and then bankrupts your sprint when you try to implement it. You need an event store, projections, snapshot strategies, a way to replay history, and a team that understands why you can't just UPDATE a row anymore. For some domains—banking, audit-heavy compliance, truly distributed systems—it's worth the cost. For the rest of us, it's a complexity tax we can't afford.

But the benefits of Event Sourcing are real. An immutable record of what happened. The ability to understand why the system is in its current state. Traceability across complex workflows. I wanted those benefits without the infrastructure.

That's when I realized CMDx already gives you most of it for free.

Why I Switched to CMDx (and How You Can Too)

Targets CMDx v1.20.

If you've been writing Ruby long enough, you've probably used at least one service object gem. Maybe you started with Interactor back when it was the default choice. Maybe you moved to ActiveInteraction for its ActiveModel-like validations. Maybe you tried Actor or LightService. I've used all of them in production, and each one taught me something about what I actually need from a command framework.

This isn't a hit piece on any of those gems—they're well-built tools that solve real problems. But after years of using them, I kept hitting the same walls. So I built CMDx to knock those walls down.

Testing CMDx Tasks Like a Pro

Targets CMDx v1.19.

I have a confession: I used to skip tests for service objects. Not because I didn't care, but because testing them was painful. Mock the database, stub the API, wrestle with instance variables, pray the test actually exercises the code path you think it does. The friction was real, and it showed in our coverage numbers.

When I built CMDx, I made a promise to myself—if the framework isn't dead simple to test, it's not done. Every task takes data in and pushes a result out. No hidden state, no side-channel mutations, no surprises. That makes testing almost enjoyable. Almost.

Building Production-Ready Rails Applications with CMDx: A Complete Guide

Targets CMDx v1.17.

I've been building Ruby on Rails applications for over a decade, and if there's one thing that keeps me up at night, it's the state of business logic in most codebases. You know what I'm talking about—fat controllers, bloated models, service objects that look like they were written by five different people on five different days. We've all inherited that one OrderService class with 800 lines of spaghetti code and a comment at the top that says "TODO: refactor this."

This guide is everything I wish I had when I started taking service objects seriously. We're going to build a complete order processing system from scratch, and by the end, you'll understand how CMDx transforms chaotic business logic into clean, observable, maintainable code.