Skip to content

8. Views

The model is one. The audiences are many. A platform engineer cares about the service topology; a security reviewer cares about which services touch PCI data; a product manager cares about the user-facing flow. They all need different diagrams of the same underlying architecture.

A view is a saved lens over the model: what to show, how to represent it, how to style it. Views never modify the model; they only choose how to display it.

view PaymentsLandscape {
"Payments domain, grouped by owning team"
show @@domain:"Payments"
hide database
group by @@team
}

Read top to bottom: show every element carrying the domain: "Payments" aspect, hide the databases, group what’s left by team.

Three ideas run through everything about views:

  1. The board is a view. Views are the only render path. Opening a model with no view renders a synthesized default board; opening a process tab renders a synthesized flow view. There is no second pipeline to drift from the first.
  2. Views never contain truth. A view selects, represents, and restyles — it never declares structure, edges, or aspects. Those are the model’s. Style rewrites apply at render only; every selector and getter reads the real model underneath.
  3. Clauses are ordered. A view body is a header zone (description, knobs) followed by clauses read top to bottom — show, then hide, then group, then style. A reviewer reads a view the way it evaluates.

Without views, you have one diagram: every module, every interface, every process arrow, all at once. For anything beyond a tiny system, that diagram is unreadable.

Views let you slice:

  • Show only the Payments domain.
  • Group the architecture by team for an org chart of the system.
  • Hide everything outside PCI scope for a security review.
  • Colour every service by the zone it lives in, and flag the edges that break a policy.

Each view is a saved query. Recipients open the same view URL and see the same diagram.

Every selection clause takes a selector — the same small query language used everywhere else in ArchLang (a bare name, a sort like service or database, an aspect atom like @@domain:"Payments", an edge pattern like A > B, joined with and / or / not / in).

  • show <sel> — union the matches into the view. Repeat it freely; each show adds more. An edge argument (show A > B) pulls the edge and its endpoints in — an edge can’t render without its ends.
  • hide <sel> — subtract, after the show-union. hide database drops the databases; hiding a mid-tree element re-parents its visible children to the nearest surviving ancestor.
  • focus <sel> — emphasis only. It never changes what is shown; it marks the matches visually so the eye lands on them.
view CheckoutSurface {
show @@domain:"Orders" or @@domain:"Payments"
hide database
focus Gateway
}

A view with no show shows everything — the default board written out is just show *.

group by <getter> clusters the shown nodes by the value of an aspect. The getter is sigiled like every getter — @@ names the aspect axis:

view ByTeam {
show service
group by @@team
}
  • A second group by nests inside the first.
  • A multi-value aspect places the element in each of its groups.
  • Elements without the value collect in an ungrouped pool.
  • Group order is lexicographic, so the diagram is deterministic.

There is no layout clause. Layout is the solver’s job — you don’t pick “elk” or “dagre.” When you need a node pinned to a spot, drag it in the viewer (or write style X { pin <x>, <y> }); the pin lives on the view, never on the model.

One clause restyles nodes, edges, and widget properties. The selector’s sort decides what gets restyled:

view SecurityZones {
show service or database or gateway
style * { color: colorize(@@security.zone) } // colour every node by its zone
style violating PciIsolation { color: crimson } // policy violations go red
style Payments { pin 120, 340 } // a WYSIWYG drag writes this
style service and in Payments {
widget { icon: shield; badge: @@security.zone }
rename "Cards & Payments"
}
}
  • A rewrite right-hand side is an expression. A string literal (color: "#f43"), a getter (widget.badge: @@security.zone), or a computed value all work.
  • Colour is the one render-typed leaf, so casts are explicit. color: @@team is a diagnostic (“team is a value, not a colour”); wrap it — color: colorize(@@team) — and the legend derives from the cast: colorize gives one swatch per value, heat a gradient. The legend never lies: every colour on the board traces to a legend row.
  • rename and pin are display directives, not model edits. rename "Cards & Payments" changes the label in this view only; pin fixes a position. Size, badge, and icon are widget mechanics — they go through the field overlay (widget.badge: …).
  • style violating <Policy> paints a policy’s findings (the violating atom) — governance drives visuals.
  • Bundlesstyle bundle DangerLook { … } declares a reusable style body; use DangerLook inside any style applies it.

Every rewrite applies at render only. Group by, tables, and policies always read the true model, so a renamed node is never mistaken for a model rename.

Representations — board, table, matrix, flow

Section titled “Representations — board, table, matrix, flow”

The board is the default representation — its absence. A view may carry at most one explicit representation clause instead:

(A story is not a representation and does not compete for this slot — it is an ordered walk over whatever the view already draws, so it composes with any of them. See Chapter 36.)

view ProcessAudit {
show process and in Corporate.Loans
table {
column @name
column @@team "Owner"
column "PII": exists ((* > PiiServices) and in this)
sort by @name
}
}

A getter column (column @@team "Owner") is editable — a cell edit writes the field or aspect back to the model. A titled calculated column (column "PII": <expr>) is read-only.

A dependency structure matrix over the selection. An optional body sets the axes by getter — matrix { rows @@team; cols @@team } — otherwise both axes are the shown elements. A filled cell is the dependency edges between its row and column; clicking an empty cell scaffolds the draft edge.

flow <ProcessRef> renders a process. The subject is written, not inferred:

view CheckoutWalkthrough {
"Checkout for the onboarding deck — PII touchpoints highlighted"
flow Checkout
style * > PiiServices { color: crimson }
}

A flow view has three modes, named by a token after the process:

  • plain (no token) — the flow walkthrough.
  • sequence — a UML-style sequence diagram with lifelines.
  • bpmn — a BPMN collaboration with swimlanes.

Mode is presentation truth, so a curated walkthrough ships in its intended mode. The bpmn mode takes an optional braced body that sorts the performers into lanes and groups lanes into pools:

view CheckoutOps {
"Checkout as a BPMN collaboration, laned by team"
flow Checkout bpmn {
lane by @@team // one lane per team value
pool by @@domain // group lanes into pools by domain
}
}

ArchLang has no built-in “network plane” or “security plane.” Planes emerge from the aspects you declare on modules and what a view groups on.

// Network plane — group by network segment.
view NetworkDiagram {
group by @@network.segment
}
// Business plane — group by business entity.
view BusinessDomains {
group by @@business.entity
}
// Security plane — PCI scope, grouped by zone.
view PCIScope {
show @@security.zone:"PCI"
group by @@security.zone
}

Same model, three views, three audiences. Aspects do the work; views surface the slices.

This is also how infrastructure stays out of the way. You don’t draw ServiceA → broker → ServiceB; you draw the logical call and hang the broker on an aspect (Chapter 9). A view that shows or groups on that aspect toggles the broker into sight when someone asks “which connections use which broker,” and leaves it out of every other diagram. The on clause makes this explicit — on deployment sets the plane context for the whole board. Model the system once at the business layer; reveal each infrastructure plane on demand.

A view can take knobs — named parameters bound at open time or by an instance:

view ZoneCompliance {
"One security zone and its egress; pick the zone, dial the radius"
knob zone from @@security.zone
knob depth: 1 { min: 1; max: 3 }
show @@security.zone:$zone or nodes of (@@security.zone:$zone <> * within $depth)
}
view ZoneCompliance PciProd { // an instance: binds the knob, adds a clause
zone: "PCI"
hide database
}

$zone reads the knob; ZoneCompliance PciProd is a saved instance that ships the zone already dialled to "PCI".

  • Modify the model. Adding a node to a view doesn’t add it to the architecture. Views are read-only on the canonical state — style rewrites and renames apply at render only.
  • Define new structure. A view can’t draw a synthetic edge. If you want an edge, declare a process step.
  • Freely reuse another view’s content. There’s no general extends for views — the one composition path is a view instance, which binds a knobbed view’s parameters and appends clauses (above). Shared vocabulary is a query’s job (export query).

Views carry stable IDs the same way modules and processes do. The formatter mints #v42 on save. Renaming a view is detected as a rename, not delete-plus-add.

  • A view is a saved lens over the model: show/hide/focus, group by, style, and one representation (table, matrix, or flow) over the default board.
  • Selectors do the choosing; getters are sigiled (@@team aspect, @name field).
  • style is the one rewrite mechanism — colour (via an explicit cast that feeds the legend), pin, rename, widget props, and violating <Policy> visuals.
  • There is no layout clause; layout is the solver’s, positions pin per node.
  • Planes (network, security, business) emerge from aspects + views, not from a separate ontology.

Chapter 31: Selectors & Queries → — the one query language behind show, hide, group by, style, and every policy.