The problem
Suppose you want to pull billing out of the monolith, and the diagram on the wiki shows Billing
calling Finance alone. The code has other dependencies, and finding them means reading all of it.
This is app/billing/billing/checkout.rb:8 in the example app:
def complete(order)
invoice = order.invoice || Invoice.new(account: order.account, number: order.number, issued_on: Date.current)
card = Card.for(order.account)
receipt = @gateway.charge(order.total)
Finance::Accounts.new.debit(order.account, order.total, memo: "#{order.number} #{card.masked}")
invoice.save! if invoice.new_record?
Proration.for(invoice).amount
@notifier.deliver(invoice)
Curriculum.for(order.student).unlock_paid_books(order.student)
receipt
end
The method calls into Curriculum. A static dependency graph would find that call, but it would not
say whether Billing → curriculum runs once a month or on every checkout. A static graph also
misses the calls made through send, callbacks and ActiveSupport::Notifications.
The fix
ra coupling_map counts the calls in the recorded app runs. Spec trees are left out, so these are
the calls that requests and jobs made:
$ ra coupling_map --namespace Billing
coupling_map · Billing
· pooled from 1 recorded run · 150 call trees (a namespace is a class's leading module, or for a top-level class the file's first path segment after app/<layer>/)
Billing → money 740
invoice → Billing 661 ⚠ cycle
Billing → invoice 478 ⚠ cycle
Billing → order 224
checkouts_controller → Billing 200
Billing → account 156
Billing → plan 156
Billing → invoice_line 136
Billing → Finance 116
Billing → curriculum 116
Billing → email_notifier 50 ⚠ cycle
renewal_job → Billing 24
Api → Billing 20
Billing → null_notifier 8
refund → Billing 5
The extraction plan can start from these recorded edges. Billing → curriculum ran 116 times, once
per checkout, so the plan replaces the direct call with an event. Start with the Billing ⇄ invoice
cycle: Invoice#total asks Billing::Proration for its adjustment, and Proration.for reads the
invoice back.
def complete(order)
# …
@notifier.deliver(invoice)
Billing.events.publish(:paid, order:)
receipt
end
How it works
Every recorded call has a caller and a receiver, and each of them is defined in a file and belongs
to a namespace. The tool groups the calls in the app-run trees from methods up to classes and then
to namespaces, counting them at each level, and marks an edge as a cycle when the reverse edge also
exists. A call site whose source line contains after_commit, send, public_send or
ActiveSupport::Notifications is marked with how the call was made.
break_cycle and stabilize_dep read the same recording and include spec trees. coupling_map
does the same counting over the app-run trees alone.
The hosted graph is built and is not open yet. It will pool runs from every deploy, so that the summary line counts deploys and days where it now counts one recorded run. Rendering the map in the browser is planned.
Run it on your app
The output on this page comes from our example Rails app, and it is regenerated whenever a tool
changes. These are the commands behind it. On your own app, run ra record first, then put your
own class, method or file in their place.
ra coupling_map --namespace Billing
The call into Curriculum is app/billing/billing/checkout.rb:16.
Reading the result
- The counts measure traffic. An edge that is rarely called can still be critical:
refund → Billingran five times, and the business cares about every one of those calls. - Rows are grouped by namespace. A top-level class is its own namespace, named after its file, so
invoiceandmoneyappear alongsideBillingandFinance. Nest your classes in modules, or read those rows as classes.
Every tool reads the same recording. What gets recorded and how far to trust an answer are on the questions page.
Related tools
break_cycle
Cut a dependency cycle. The files or namespaces that form a dependency cycle at runtime. Each cycle is marked with its lowest-traffic edge, which is the cheapest one to cut.
See more →stabilize_dep
Dependencies that run against stability. A stable namespace, one with many callers and few dependencies, calling into a less stable one. Instability is computed from recorded calls, and the report lists the call sites to move or to put behind an abstraction.
See more →split_package
The parts of a namespace each consumer uses. Groups the classes in a namespace by which outside classes called them, and reports the groups that share no consumer as the lines to split the namespace along.
See more →