9. Fields and Aspects
A .arch file is mostly structural: declare a module, declare an interface, declare a process. The non-structural part — owners, versions, URLs, classification axes — lives in two related but distinct constructs: fields and aspects. Together with descriptions (Chapter 10), they are the three ways to annotate any element. The crisp split:
- Aspects = connections to a different plane. An aspect links a thing to a different plane of the architecture — the data plane, the hosting plane, the messaging plane — not to a peer on its own plane.
- Fields = structured data you explicitly don’t want interconnected. A version, a status, a repo URL, a required property, a widget control. It stays local to the element.
- Descriptions = unstructured prose.
This chapter is about the first two. The short version: if it’s a property of the thing, use a field; if it’s a connection to another plane — something you’d ever group by or focus a view on — use an aspect.
Fields
Section titled “Fields”Fields live directly in a body, no surrounding block:
module Orders { status: active version: v2 repo.url: "https://github.com/acme/orders" ext.cmdb.ci: "CI28304858" base: "/orders" "Order management service"}Rule. Separate tokens with a single space. Don’t pad with extra spaces to line values up into columns — alignment looks tidy but rots, because every rename re-breaks the columns and produces noisy, alignment-only diffs.
Each field is key: value where:
- Key is an identifier or a dotted path (
repo.url,ext.cmdb.ci). Dotted keys are equivalent to a nested object structure; tooling treats them uniformly. - Value is one of four scalar types:
- identifier —
Commerce,v2 - quoted string —
"any text", multi-line allowed - number —
42,3.14 - boolean —
true,false
- identifier —
That’s the complete value language. There are no nested objects, no per-field type system, no generics, no user-defined value shapes. Tooling may interpret values by convention (URLs become clickable, dotted IDs become copy-able) but the parser treats them all as scalars.
Mindset shift. If you’re coming from configuration languages like HCL, JSON, or YAML, the absence of nested objects can feel limiting. It’s deliberate. Architecture descriptions don’t need configuration’s expressivity; what they need is a flat key-value layer that’s trivial to query and group. The dotted-key form (
repo.url) covers what nested objects would.
Bare-string descriptions
Section titled “Bare-string descriptions”A bare string is a field with a reserved meaning — it’s the description:
module Payments { "Core payment processing service for the platform."}A body may have multiple bare-string descriptions; they concatenate with \n in declaration order. This lets you write multi-paragraph descriptions without one huge multi-line string. Descriptions render as markdown plus two extensions — see Chapter 10.
Aspects
Section titled “Aspects”An aspect connects an element to a place on another plane. Aspects go inside an aspect { } block:
module Payments { aspect { team: "Payments" domain: "Payments" security.zone: "PCI" network.segment: "Internal" criticality: "High" }}Each key: value inside aspect { } is one aspect. Keys can be dotted (security.zone, network.segment); values use the same scalar types as fields. An aspect value’s lexical form carries meaning — see Classification or membership below. The keyword takes no dot after it — aspect security.zone: "PCI", never aspect.security.zone — because it heads an exclusive construct, not a field namespace.
Aspects:
- Cascade. A classification aspect declared on a parent module is visible on every nested module, surface, and interface — unless they declare the same key themselves. This is how a
domain: "Payments"on the top-level module reaches every operation inside without repetition. See Chapter 18. - Drive views.
show @@domain:"Payments"works becausedomainis an aspect key;group by @@teamgroups by theteamaspect. The@@sigil names the aspect axis in any getter or selector. - Drive checks. Validators read aspects: every PCI service must have a recorded
security.contact, no service innetwork.segment: DMZmay call a service innetwork.segment: Internal.
One line or a block?
Section titled “One line or a block?”A single aspect doesn’t need a block. Write it inline as aspect key: value:
module Cart { aspect domain: "Payments"}Choose by how many values you’re writing:
- One value → a single line.
aspect domain: "Payments". - A couple (≈2) → either. An
aspect { }block or two single lines; your call. A short inline block is fine too —aspect { domain: "Payments"; criticality: "High" }— but cap inline blocks at 2 pairs, 3 at the very most, then break onto multiple lines. - Many (≈5+) → prefer a block. A single-line form isn’t forbidden, but an
aspect { }block reads better at that size.
Neither notation is ever mandatory — these are preferences, not rules. (Inline block entries are separated by a semicolon; a comma between entries is a parse error. A newline is the idiomatic separator in a multi-line block.)
Classification or membership: the value decides
Section titled “Classification or membership: the value decides”An aspect value’s lexical form decides what it means. This is the one place where quoting versus not-quoting changes semantics:
aspect { domain: "Payments" // classification — a shared group identity security.zone: "PCI" // classification netzone: Dmz // membership — Dmz becomes a place on plane `netzone`}- String value (
key: "x") → classification. A pure identity keyed by(key, value). Every occurrence of the same(key, "x")denotes the same implicit place, so classifications rename as a unit and form an invisiblemember → groupoverlay. Identity is per-key:netzone: "dmz"andtier: "dmz"are different aspects that happen to share a value. A list value is multi-membership —tag: ["edge", "public"]joins both classifications. - Bare value (
key: x) → membership at the modulex. This is how an aspect is materialized:xbecomes a real, drawable place on planekeyinstead of a synthesized node. Bare refs are strict — a value that resolves to no module isASPECT_BAD_REF; the editor’s quick fix quotes it, turning it into a classification. Quoting is the escape hatch whenever you mean a tag, not a module.
When in doubt, quote it. A quoted classification is always valid; a bare membership ref has to resolve. Most classification axes (domain, security.zone, criticality) are classifications, so most aspect values are strings.
In a view, the
@@sigil marks the aspect axis and the same quoting rule applies:show @@domain:"Payments"matches the quoted classification,group by @@network.segmentreads the axis. See Chapter 8.
Keys are planes, values are places
Section titled “Keys are planes, values are places”The mental model behind aspects: the key names a plane of existence (security.zone, broker, host) and the value is a place on it (DMZ, kafka-broker, that Postgres instance). Two elements that share a key and value live in the same place on the same plane — a weak “these belong together” link, not a hard call edge. That’s why nesting and aspects do different jobs: nesting is domain depth (B is part of A), while an aspect is a connection to a different plane (B is deployed on / routed through X).
This is what lets you model the business layer without the transport plumbing. Don’t draw ServiceA → Kafka broker → ServiceB — that buries the real dependency under infrastructure and points every service at the same broker node. Instead draw the logical call (ServiceA calls the kafka interface on ServiceB) and put the broker on an aspect:
module ServiceB { kafka orderEvents { aspect broker: KafkaBroker }}The broker becomes a materialized membership living on the data plane, surfaced as a toggleable layer when you want to see which connections use which broker — not a waypoint in the call graph. The hosting plane chains the same way: a nested database carries an aspect dbms: ... to the DBMS it runs on, which carries an aspect host: ... to its server. Each hop is a different plane, so each is an aspect — never nesting, which would wrongly claim “part of the domain.”
Field or aspect?
Section titled “Field or aspect?”The rule of thumb again, with examples:
| Use an aspect when | Use a field when |
|---|---|
You’d ever group by it | The value is a single fact unique to this thing |
You’d filter a view on it (show @@…) | The value is a URL, ID, version, or count |
| It’s a classification axis | It’s an identifier or pointer |
Examples: domain, security.zone, team, criticality | Examples: repo.url, version, ext.cmdb.ci, base |
team sits on the aspect side — the stdlib defines it as aspect team, a cascading classification axis. By the rule above it belongs there: you group by @@team and slice views by it, which is exactly what makes something an aspect rather than a field. The choice was made once in the stdlib so every project shares the same convention.
Note one practical detail: aspects are always cascade-override in propagation behavior. You can’t have an “append” aspect. If you need accumulation (a list of tags that grows from parent to child), use a field with the append modifier — see Chapter 18.
Multi-line strings
Section titled “Multi-line strings”Quoted string values can span multiple lines. The lexer auto-dedents:
module Payments { " Core payment processing. Owns authorization, capture, and refund. Publishes events to the order pipeline. "}A leading whitespace-only line is dropped; the minimum indent shared by remaining lines is stripped. The above produces the value "Core payment processing.\nOwns authorization, capture, and refund.\nPublishes events to the order pipeline.".
The only recognized escape is \".
Triple-quoted strings
Section titled “Triple-quoted strings”For values that contain double quotes (HTML, JSON snippets, regex), use triple-quoted strings. Backslashes are not escape sequences inside them:
module Cart { widget: """ <div class="card" data-name="{{name}}"> <span class="label">{{team}}</span> </div> """}Triple-quoted strings are raw. They don’t support ${expr}-style interpolation (so future template-literal features can be added later without conflicting).
No props { } block
Section titled “No props { } block”Earlier ArchLang had a props { } block to hold “extra metadata.” That’s gone. The role is fully covered by fields with dotted keys: ext.cmdb.ci: "...", repo.url: "...". If you’re reading old files with props { }, run the formatter — it’ll lift them out.
Summary
Section titled “Summary”- Three ways to annotate: aspects are connections to a different plane, fields are structured data you don’t want interconnected, descriptions are unstructured prose.
- Fields describe what a thing is and live directly in the body, no surrounding block.
- Aspects classify a thing along an axis (a plane); they live in
aspect { }or as a singleaspect key: valueline. - Format by value count: one value → a single line; several → a block; cap inline blocks at 2–3 pairs, semicolon-separated (never a comma). Single spaces, never column alignment.
- Values are scalars: identifier, quoted string, number, boolean. No nested objects.
- Bare-string descriptions are reserved; multiple bare strings concatenate.
- A string value is a classification (a shared group identity); a bare value is membership at a real module, which becomes a place on that key’s plane. Aspects cascade with override; if you need accumulation, use a field with
append. - The choice “field or aspect?” is usually: would you
group byorfocuson it?
What’s next
Section titled “What’s next”Chapter 10: Descriptions → — the markdown subset and cross-reference syntax inside description strings.