Start with the constraint that Rails service extraction would remove. Perhaps one workflow needs an independent release cycle, has a measured resource profile unlike the rest of the application, or has an operating owner who can take responsibility for a separate service. A large directory or an unfamiliar part of the codebase is not enough to establish that benefit.
Before moving the workflow across a process boundary, give it an explicit input, output and owner inside the application. That work improves understanding even if the eventual decision is to keep it in the Rails codebase. It also exposes how much information a separate service would need.
Compare a module with a separate deployment
Shopify’s 2019 account of componentization describes choosing a modular monolith rather than introducing separate deployment units. It is a useful alternative to consider, not proof that every Rails application should make the same choice.
For a particular workflow, record the current change frequency, dependencies, resource measurements and support responsibility. Then describe what extraction would change. A separate service introduces a contract that callers must understand and failures they must handle. It may also require another release pipeline, credentials, monitoring and an explicit source of truth for data.
The comparison should include a module with a clear interface. Otherwise the proposal can mistake the value of a better boundary for the value of a network hop.
Test the boundary before measuring its speed
A small Ruby experiment used a deliberately synthetic load-capacity policy. It totals input weights and rounds them into units of 1,000 grams. This is a simple divisible-load rule, not a real packing or fulfilment algorithm.
The same policy ran as a local module and in a separate Ruby process receiving JSON through standard input. Five input cases produced identical results across the two paths. A version field made the policy choice explicit. An unsupported version and a string where an integer was required were rejected.
The unsafe adapter divided a request containing 200 and 300 grams into two calls, then added the rounded results. It returned two capacity units. Passing the complete input to one child-process call returned one unit, matching the local calculation. Moving the code had exposed an incorrect decision about where aggregation belonged.
| Arrangement | Result for the selected input | Child-process requests |
|---|---|---|
| Local module with complete input | 1 capacity unit | 0 |
| Per-item adapter that sums rounded outputs | 2 capacity units, incorrect | 2 |
| Batch adapter with complete input | 1 capacity unit | 1 |
Six tests passed with 18 assertions on 20 September 2026 using Ruby 3.3.3. An injected child exit also produced an explicit unavailable-result error instead of an empty successful response. The experiment used real child processes but no network, HTTP service, remote deployment or Rails application. Its request counts are observed; they are not latency or throughput benchmarks.
Decide whether Rails service extraction is justified
The fixture demonstrates a contract error and an added failure case. It supplies no evidence that this calculation needs a service. For this synthetic example, retaining the module is the supported starting choice. Independent scaling or deployment remains a hypothesis requiring measurements from a real application.
A production decision should also establish how callers behave during an outage, which contract versions can coexist, and who owns rollout and recovery. If the workflow changes state, investigate transaction and reconciliation boundaries separately. A fallback that is harmless for a pure calculation may repeat a payment or write when applied to a stateful operation.
Use shadow comparison only where it is safe to execute the selected work twice. Compare results on permitted representative inputs, define the acceptance threshold before reviewing them, and keep a deliberate route back while that route is still compatible with stored data and callers.
A useful Rails delivery engagement can therefore begin with a boundary and decision record. It should name the constraint, compare the module and service options, test the proposed contract, and assign operational ownership. The outcome may be an extraction, a better internal module or a decision to leave the workflow alone until the evidence changes.
The executable experiment and evidence record preserve the tested cases and their limitations.
Prepared and executed with AI assistance using synthetic inputs. Independent AI reviews are recorded separately from the byline owner’s approval; no human engineering sign-off is claimed.
Download the experiment
Download the runnable Rails examples and evidence summary (ZIP, 36 KB). This article’s example is in q08/; the shared setup and reproduction instructions are in README.md. The package contains eight synthetic examples, checked on 20 September 2026. The recorded runtime, provider fakes and limits are documented inside.