Skip to content

2. Your First Architecture

This chapter walks through building a complete (small) architecture from an empty directory. By the end you’ll have three modules, one process tying them together, a diagram, and a feel for the shape of the language.

We’re going to use only the bare language — the base types module, surface, interface plus the top-level constructs process and view — with no standard library. Later chapters introduce richer types (service, command, event); Chapter 11 covers them in detail. Starting bare keeps the focus on what the language itself does, separate from convention.

We’ll model a payments domain: a customer pays, a payments service authorizes, a ledger records. Nothing else.

Every project starts with a manifest. Create package.archspace:

package: shop

One line. The package: field names the package root; we don’t import the stdlib yet because we’re using bare types only. (Package names are lowercase, dot-separated — a bare shop is fine for a single project.)

Chapter 12 covers the manifest in depth. For now this is enough.

Add payments.arch in the same directory:

module Payments {
aspect team: "Platform"
"Authorizes and captures card payments."
interface authorize
interface capture
interface refund
}
module Ledger {
aspect team: "Finance"
"Immutable financial record of every transaction."
interface record
}
module Customer {
"The person initiating the payment."
}

You’ve declared three modules using the bare module type. Each has:

  • A type (module) — the base type of the language.
  • A name (Payments, Ledger, Customer) — how the rest of your model refers to it. Module names are UpperCamelCase.
  • A team aspect — who owns it (optional; aspects cascade to everything nested, with override).
  • A description — the bare string literal. Plain markdown, plus two extensions you’ll meet in Chapter 10.
  • Zero or more interfacesinterface authorize, etc. These are the things other modules can ask this one to do. Interface names are lowerCamelCase and read like the real operation — a verb for an RPC call (authorize), an action-plus-resource for REST (Chapter 5 goes deeper).

Customer has no interfaces. That’s fine — not every module exposes operations. (Stdlib types add semantic distinctions like user for entities that only originate calls; for now everything is just module.)

We didn’t sit down and ask “what connects to what.” We described each module on its own — what it is, what it can do — and stopped. Connections come later, and they fall out of a process rather than being drawn. That two-pass habit (describe the cast, then tell the story) is the backbone of authoring in ArchLang.

Try it live — edit the source above, the diagram updates below. Hover identifiers, press F2 to rename, Ctrl/⌘ Space for completion:

Loading editor…

Save the file and run:

Terminal window
archlang validate .

You should see no errors. Open the directory in your editor (with the ArchLang extension installed) and trigger the inline preview pane — three boxes appear, connected to nothing yet. That’s expected — we haven’t said how they interact.

The diagram looks plain because the bare module type has no specialized rendering. Boxes labeled with names; one default widget for all three. The stdlib introduces per-type widgets (Chapter 11); for this chapter, plain boxes are the point — they make the underlying structure obvious.

Add checkout.arch:

process Checkout {
Customer > Payments.authorize
Payments > Ledger.record
Customer > Payments.capture
Payments > Ledger.record
}

A process is a sequence of steps. Each step has the form Caller > Callee.Interface:

  • The caller (left of >) is a module — who is making the call.
  • The callee (right of >) is an interface — the specific operation being invoked.

Save and the preview updates. The diagram now has arrows. They weren’t drawn — they were derived from the process. This is the first commitment from the foreword in action: behavior is the source of structure.

Mindset shift. In most diagramming tools you draw an arrow because two services talk. In ArchLang you declare a process step; the arrow appears because something declared that it talks. Delete the step and the arrow disappears. Add another step and a new arrow appears. The dependency graph is always a function of behavior.

Open payments.arch and add a fourth interface:

module Payments {
aspect team: "Platform"
"Authorizes and captures card payments."
interface authorize
interface capture
interface refund
interface void // new
}

Save. The preview updates. void shows up in the Payments node; no arrow connects it to anything because no process invokes it yet.

Now open checkout.arch and add a step:

process Checkout {
Customer > Payments.authorize
Payments > Ledger.record
Customer > Payments.capture
Payments > Ledger.record
Customer > Payments.void // new — customer cancels mid-checkout
}

Save. The preview updates again. void now has an arrow into it.

You’ve added an interface and a process step in two edits and the diagram followed along. No layout to redo, no boxes to drag.

shop/
├── package.archspace
├── payments.arch
└── checkout.arch

Three files, three modules, one process. A complete (if minimal) architecture written in pure bare language. Every diagram, every dependency arrow, every impact analysis that ArchLang can do for you is derived from these files.

  • A workspace starts with a package.archspace manifest naming the package.
  • The bare types module and interface are enough to model structure and exposed operations.
  • Processes are sequences of Caller > Callee.Interface steps. Arrows in diagrams are derived from processes — they are not drawn directly.
  • The editor preview re-renders on save. The dev loop is edit file → see diagram.

Chapter 3: Reading a Diff → — make a change to the architecture and see how ArchLang shows you what changed.