Flows
Flows describe end-to-end business interactions — "place an order", "open an account", "settle a trade" — as ordered sequences over the relationships you've already declared. They're how you document what actually happens at runtime, not just what's connected to what.
Why flows exist
Nodes and relationships tell you the topology — what can talk to what. They don't tell you the story: which calls happen in which order to satisfy a given business outcome. A trader places an order — what's the path through your system?
Without flows, that knowledge lives in tribal docs, Confluence pages, or nowhere. With flows, the sequence is declared in CALM, validated on every PR (so it can't reference an edge that no longer exists), and surfaced in the dashboard's visualizer for new joiners.
unique-id — the relationship itself
is declared once in the relationships array. Removing the relationship
breaks the flow (and ArchRails fires AR-FLOW-001 on the PR that
introduces the break).
Anatomy of a flow
Flows live in the top-level flows array of a CALM file (same level
as nodes and relationships). Minimum shape:
{
"unique-id": "place-an-order",
"name": "Place an order",
"description": "Customer submits an order via the portal, which is validated and persisted.",
"transitions": [
{
"relationship-unique-id": "portal-to-orders-server",
"sequence-number": 1,
"direction": "source-to-destination"
},
{
"relationship-unique-id": "orders-server-to-orders-db",
"sequence-number": 2,
"direction": "source-to-destination"
}
]
}
Four fields:
unique-id— kebab-case, unique within the CALM file.name— human-readable, surfaces in the dashboard.description— one sentence. Helps reviewers know what this flow is for without tracing every transition.transitions— ordered list. The actual work.
Transitions and direction
Each transition references one relationship by its unique-id, gives
it a place in the sequence, and declares which way the call goes:
relationship-unique-id The relationship being traversed in this step. Must match a relationship declared in this CALM file (or, with federation, in a linked one). sequence-number 1-indexed integer. Gaps are tolerated; duplicate sequence numbers are allowed only when steps happen in parallel. direction One of: "source-to-destination" — call goes the way the relationship was declared. "destination-to-source" — call goes the other way (response, callback, reverse SSE).
Most transitions are source-to-destination — you declared the
relationship in the direction calls actually flow. Use
destination-to-source when the same edge carries both directions
(websocket, gRPC streaming, server-sent events) and the flow needs to model the
reverse leg explicitly.
Worked example · place an order
Assume an architecture with these relationships already declared:
customer-clicks-portal interacts: customer → customer-portal portal-to-orders-server connects: customer-portal → orders-server orders-to-risk connects: orders-server → risk-engine orders-to-db connects: orders-server → orders-db orders-to-cache connects: orders-server → orders-cache
The "place an order" flow stitches them together:
{
"unique-id": "place-an-order",
"name": "Place an order",
"description": "Customer submits, risk approves, order persists, cache primed.",
"transitions": [
{
"relationship-unique-id": "customer-clicks-portal",
"sequence-number": 1,
"direction": "source-to-destination"
},
{
"relationship-unique-id": "portal-to-orders-server",
"sequence-number": 2,
"direction": "source-to-destination"
},
{
"relationship-unique-id": "orders-to-risk",
"sequence-number": 3,
"direction": "source-to-destination"
},
{
"relationship-unique-id": "orders-to-db",
"sequence-number": 4,
"direction": "source-to-destination"
},
{
"relationship-unique-id": "orders-to-cache",
"sequence-number": 4,
"direction": "source-to-destination"
}
]
}
Notice that the DB write and cache prime share sequence-number: 4
— they happen in parallel after risk approval. That's the model: each
sequence-number is one "step," and a step can contain multiple parallel
transitions.
Cross-repo flows
Most non-trivial flows cross repo boundaries — the portal is in one repo, orders in another, risk in a third. Two ways to model this:
- Author the flow in the repo that owns the entry point. The portal repo declares the flow; the transitions reference relationships that live in other repos via federation. ArchRails resolves them at validation time using the same cross-repo machinery from lesson 3.
- Author the flow in a "contract" repo dedicated to cross-cutting concerns. The contract repo has no services of its own — just CALM relationships and flows describing how the others fit together. Common in larger orgs where the architecture team owns the contract layer.
Either way, the relationships the flow references need to be reachable through
federation — either declared in this CALM file or in a federated one with
a discoverable repository field on the relevant node or endpoint.
Visualizing in the CALM Visualizer
The dashboard's CALM Visualizer renders each declared flow as a numbered sequence overlay on the graph — useful for onboarding new engineers ("here's what happens when a customer places an order") and for incident review ("the page came from this hop and propagated this far"). Open the visualizer from Dashboard → CALM Visualizer and pick a flow from the side panel.
What ArchRails enforces
Flows are validated on every PR:
-
AR-FLOW-001— a transition references a relationship that doesn't exist (typo, edge removed in this PR, edge declared in a repo that's no longer federated). Most common when someone deletes a relationship without noticing a flow depended on it. -
AR-FLOW-002— transition is missing required fields (relationship-unique-idorsequence-number). Schema-level catch. -
Sequence integrity — gaps in
sequence-numberare tolerated; parallel transitions sharing a number are allowed. Out-of-order numbers are flagged as a warning.
Flows can also carry their own controls block — useful for
requirements that apply across the whole transition sequence ("every flow that
touches PII must complete within 30 seconds and emit one audit log per
transition"). See lesson 5 for the
controls syntax.
You've completed the foundations track. Open a real PR in your own repo and watch how ArchRails reads the architecture you've just learned to author. Questions? Reach the team via the Dashboard → Contact Us button.