4. Modules
A module is an architectural unit with a single accountable maintainer, a clear boundary, and a public surface composed of interfaces. It is the primitive everything else attaches to.
module Payments { team: Payments "Authorizes and captures card payments."
interface Authorize interface Capture interface Refund}That’s a module. The kind is module; the name is Payments; the field team says who owns it; the description and the three interfaces flesh out what it does.
This chapter and the next few use only the language’s bare kinds — module, facet, interface. The standard library adds richer kinds like service, database, command, event (see Chapter 11). Everything in this chapter applies to those too; they are subtypes of module.
The rule
Rule. If it has an owner and a boundary, it’s a module.
That’s the entire test. Services are modules. Databases are modules. External systems are modules. People and external clients (actors) are modules. Subsystems containing other modules are modules. A “library” sitting inside one team’s repo, with its own contract surface, is a module.
If you find yourself asking “is this thing a module or something else?” — and it has an owner and a boundary — it’s a module.
The bare module kind
The base kind is module. It carries no required fields, no default interfaces, and no special widget. It is the most generic possible module declaration:
module Orders { interface CreateOrder interface CancelOrder}That’s a complete module. Two interfaces, no team, no description, no labels. The validator accepts it. The diagram renderer draws it as a labeled box.
Most architectures don’t use bare module directly. They use a stdlib kind like service or a project-defined kind like payment_service, both of which are subtypes of module that add team requirements, widgets, and conventions. But the bare form is always available and is what every richer kind reduces to.
Nesting
Modules can contain other modules. Use either form:
Nested directly:
module Platform { team: "Platform Engineering"
module AuthService { interface Authenticate }
module UserService { interface GetUser }}Flat with in:
module Platform { team: "Platform Engineering"}
module AuthService in Platform { interface Authenticate}
module UserService in Platform { interface GetUser}Both produce identical structure. The in form lets you keep a parent’s declaration in one file and let teams add child modules from their own files without all editing the same place.
The viewer renders nested modules as containers. A module Platform becomes a frame; AuthService and UserService become nodes inside it.
Fields
The body of a module is mostly fields. A field is key: value:
module Payments { team: Payments repo.url: "https://github.com/acme/payments" version: v2 ext.cmdb.ci: "CI28304858" "Core payments service"}Field keys can be dotted (repo.url, ext.cmdb.ci). Values are identifiers (Payments, v2), quoted strings, numbers, or booleans. That’s the entire value language — no nested objects, no per-field types. Chapter 9 covers fields in depth.
The bare string "Core payments service" is a special field: the description. Module bodies may have multiple descriptions; they concatenate. Chapter 10 is dedicated to them.
Interfaces
Interfaces are how modules expose themselves to other modules. Inside a module body, interface declarations sit alongside fields:
module Orders { team: Commerce
interface CreateOrder interface CancelOrder interface GetOrder interface OrderEvents}The keyword interface is the base interface kind. The stdlib defines subtypes like command, query, and event that carry semantic information (synchronous vs async, read vs write). For now, every interface is just interface. Chapter 5 covers interfaces in detail; Chapter 11 introduces the stdlib interface kinds.
Interfaces are always leaves: an interface does not contain another interface. To group them, use facets — Chapter 6.
Stable IDs
After you save a .arch file through the formatter, each module gains a stable ID:
module #pay001 Payments { team: Payments}The #pay001 prefix anchors the module’s identity across renames. Rename Payments to PaymentsService and the ID stays the same — tools and diffs know it’s the same module. Chapter 13 returns to this in depth. For now: don’t write IDs by hand; let the formatter mint them.
Empty bodies
If a module has nothing to add, the body can be omitted entirely:
module NotificationsThat’s valid. The module exists with no fields and no interfaces. Useful as a placeholder or when the module’s identity is the entire architectural fact.
Summary
- A module is anything with an owner and a boundary.
- The bare kind is
module. Every richer kind (service, database, actor, …) is a subtype. - Bodies contain fields, descriptions, interfaces, facets, and nested modules — in any order.
- Modules can nest directly or attach to a declared parent via
in Parent. - Stable IDs (
#xyz) anchor identity across renames; the formatter mints them on save.
What’s next
Chapter 5: Interfaces → — how modules expose themselves to each other.