The problem
You want to pull billing out of the monolith. The diagram on the wiki says Billing talks to
Accounts and nothing else. The code says otherwise, but only if you read all of it:
module Billing
class Checkout
def complete(order)
Gateway.new.charge(order.total)
Finance::Accounts.debit(order.account, order.total)
Curriculum.for(order.student).unlock_paid_books
end
end
end
There’s a call into Curriculum in there, and a static dependency graph would find it. What it won’t
tell you is how much it matters. Is Billing → Curriculum one call a month, or on every checkout?
And a static graph misses the calls made through send, callbacks and ActiveSupport::Notifications,
which is where the surprising coupling usually hides.
The fix
Draw the map from production traffic instead:
$ ra coupling_map --namespace Billing
pooled from 214 deploys · 91 days
Billing → Finance::Accounts 4.1M calls every checkout
Billing → Curriculum 3.9M calls every checkout ⚠ not on the wiki
Billing → Gateway 4.1M calls every checkout
Reporting → Billing 96 calls month-end only
Billing ⇄ Enrollment 1.2M calls cycle: via after_commit callback
Now the extraction plan starts from the real edges. Billing → Curriculum fires on every checkout,
so it becomes an event rather than a direct call. Reporting → Billing is 96 calls a month and can
move to a nightly export. And the cycle through an after_commit callback is the edge to cut first:
module Billing
class Checkout
def complete(order)
Gateway.new.charge(order.total)
Finance::Accounts.debit(order.account, order.total)
Billing.events.publish(:paid, order:)
end
end
end
How it works
Every recorded call has a caller and a receiver, and each lives in a file and a namespace. The hosted graph rolls those calls up from methods to classes to namespaces, counting them as it goes, across every deploy you record.
The result is a weighted graph of your app’s modules, where each edge’s weight is real production
traffic. It’s rendered as a map you can open in the browser, and served over MCP so your agent can
plan a refactor from it. break_cycle and stabilize_dep run locally on your suite’s recording;
coupling_map is the same idea, weighted by what your users actually do.
Limits
- Traffic, not importance. A rarely called edge can still be critical: the month-end report runs 96 times, and the business cares about every one.
- Only what you record. Services and workers you didn’t instrument don’t appear.
- Namespaces are your namespaces. The map follows your module structure. If everything lives in
app/modelswith no namespaces, roll it up by directory instead.
Related tools
break_cycle
Cut a dependency cycle. Files or namespaces caught in a runtime dependency cycle, flagged with the single lowest-traffic edge to sever to break the loop.
See more →stabilize_dep
Point dependencies at stability. A stable module, with many callers and few dependencies, reaching into a less stable one. Measured from real call traffic, so you can depend toward stability or put an abstraction between them.
See more →split_package
Split a package used in parts. Consumers that pull in a whole namespace but touch disjoint pieces of it. Split it so nobody takes a dependency on code they never call.
See more →