Every established enterprise has a batch layer. Nightly jobs that move records between systems, month-end runs that calculate what people get paid or billed, exports that regulators expect on schedule. It is the least fashionable software in the building and some of the most important, and in legacy estates it is typically held together by cron, scripts, and the vigilance of whoever gets the failure emails.
When batch workloads come up for modernization, the conversation usually fixates on language: what do we rewrite the jobs in? That is the smaller question. The bigger one is what has been missing all along: durability. Here is the pattern we reach for, and why.
What cron-and-scripts actually lacks
The failure modes of traditional batch are so familiar they stop being seen as defects:
A job dies at 2 AM, halfway through. Did the first half commit? Can you rerun it, or will rerunning double-process four thousand records? The person who knows is asleep, and the answer lives in their head, not in the system.
A job silently stops running. Nobody notices for weeks, because the signal for success and the signal for "never started" are identical: silence.
State lives everywhere and nowhere. Progress is implied by rows in tables, files in directories, flags with names like processed_v2. Reconstructing what happened requires archaeology.
Retries are manual folklore. "If the export fails, delete the temp file, reset the flag, run it again, but not on Mondays." Every legacy shop has these incantations. They are institutional risk wearing the costume of institutional knowledge.
None of this is fixed by rewriting the job logic in a nicer language. It is fixed by changing where execution state lives.
What durable orchestration changes
Temporal is a workflow orchestrator whose core idea is that workflow state is persisted, event by event, outside your worker processes. A workflow describes the sequence: fetch these records, transform them, write the results, notify on completion. Activities do the actual work, and each activity's outcome is recorded durably before the workflow moves on.
The consequences map one-to-one onto the failure list above. A worker dies mid-run: the workflow resumes from the last completed step, on another worker, without human intervention and without redoing committed work. Retries are policy, declared per activity, not folklore. The 2 AM question, "what state is it in?", has an answer you can look up: every workflow's history is inspectable, showing exactly which steps completed with what inputs. And "the job never started" becomes a visible, alertable condition rather than silence.
The mental shift for teams coming from cron is that idempotency and retry semantics move from tribal knowledge into the code's structure. The system enforces what the folklore used to only recommend.
Where Rails fits
Temporal orchestrates; it does not want to own your domain logic. That belongs in an application layer, and Ruby on Rails remains an excellent one for exactly the shape batch work takes: heavy database interaction, well-understood models, validations and associations that encode business rules, and a mature ecosystem for everything around the edges. Activities call into the same domain model the rest of the application uses, which means the business rules live once, tested once, rather than being reimplemented in each job script.
The pairing is deliberately unexciting: Rails for the domain, Temporal for durability, workers deployed like any other service. Batch stops being a separate, stranger world with its own failure culture and becomes ordinary application code with extraordinary execution guarantees.
The migration discipline that makes it safe
Rebuilding a legacy batch job is riskier than building a new one, because the legacy job's real specification is its behavior, not its documentation. Two practices carry the weight:
Extract the behavior contract first. Before writing the replacement, document what the existing job actually does: inputs, outputs, edge cases, side effects, the quirks downstream systems have come to depend on. The old code is read as a specification to be recovered, not just source to be translated. This is painstaking, and it is where AI-assisted analysis has changed the economics of the work, making it feasible to recover complete behavior contracts from code nobody fully remembers.
Validate by running both. The new workflow runs alongside the legacy job against the same data, and outputs are compared empirically until parity is demonstrated, not asserted. Only then does the old job retire. "It passed the tests we thought to write" and "it produces what the legacy system produces" are different confidence levels, and month-end financial jobs deserve the second one.
Modernized this way, the batch layer usually ends up as the most observable, most recoverable software in the estate, a strange reversal for the code that used to be the scariest. If your batch layer is due, our Ruby on Rails practice does this work, behavior contracts and parity validation included, and it starts with the same modernization triage as everything else.
