Skip to content

11. The Standard Library

Up to this point we’ve used only the bare language: module, surface, interface. Three base types. Every example has been a labeled box with operations and arrows.

That’s enough to model anything structurally, but it’s missing two things real projects need:

  • Visual distinction. A database, a service, a user, and an external vendor should look different in a diagram. With only module, they don’t.
  • Enforced conventions. Every service in this org has a team owner. The bare module type doesn’t enforce anything; the validator can’t tell a service from an external client.

The standard library solves both. It ships packages of types — arch.extras, arch.c4, arch.backend, arch.diagrams — that introduce semantic subtypes of the base types. Each one cascades a default widget and (usually) requires a few fields.

Rule. The stdlib is optional batteries, not a mandate. It’s a good, exhaustive default set bolted onto a bare language — lean on it to get going, but you’re free to build your own library from scratch, and starting on stdlib types doesn’t lock you in. You can swap a stdlib type for your own later, or extend one (add requirements, fields, relating options) rather than rewrite it.

This chapter introduces what’s in the stdlib and how to bring it into your project.

Declare the types you need in your package.archspace, so the palette is space-wide — every .arch file in the package can reach it, and the manifest doubles as a discoverable list of what’s in play:

package: my-project
// Treat each package as a palette — pick the types you actually use.
use service, database, message_broker, gateway from arch.backend
use rest_create, rest_read, rest_update, kafka, grpc_unary from arch.backend
use user, usergroup from arch.extras
use table, column from arch.diagrams

Rule. Never use * the standard library. Treat a stdlib package as a pick-and-choose palette and import the handful of types you need by name. A wildcard drags in the package’s entire surface and couples you to all of it. (use * is reasonable only for a small, company-internal library you wrote yourself — never for the stdlib.) Keep these use lines in the package.archspace manifest rather than re-importing file by file: one space-level list is self-documenting.

arch.backend is the pragmatic architecture vocabulary: the dominant backend module types (service, database, gateway, message_broker, …), the general system / frontend / component / external_system types, and the wire-level interface types (http_*, grpc_*, kafka, amqp, db_read, db_write, …). arch.extras ships generic device/people figures (user, usergroup, laptop, server, cloud, …). arch.diagrams adds table / column for ER-style schemas. All packages auto-resolve against the toolchain’s bundled stdlib — no dependencies entry needed.

Prefer the formal C4 model? arch.c4 is a complete, self-contained C4 vocabulary — person, software_system, container + form-factor subtypes, component, the external/boundary variants, and deployment nodes. Import the C4 types you decompose with — use person, software_system, container, component from arch.c4 — and stay in C4’s fixed set of abstractions. A library like this is more than convenience: C4 is a meta-model, a notation and discipline that shapes how you decompose and reason. You adopt it for what it does to your thinking, not just for the boxes it hands you. (Contrast arch.extras, whose user / group figures are pure nice-to-haves that make a diagram more vivid.)

Now your .arch files can use the richer types:

service Payments {
aspect team: "Payments"
aspect { domain: "Payments" }
rest_create authorize
kafka paymentEvents
}

The types you import are subtypes of module and interface. Every rule from chapters 4 and 5 applies — they just add defaults and requirements on top.

The dominant backend module types:

TypePackageRequired blanksDefaultsDefault widget
servicearch.backendaspect team, aspect domainarch-module
databasearch.backendaspect team, aspect data.classificationarch-backend-database
message_brokerarch.backendaspect team, aspect domainarch-module

The columns:

  • Required blanks must be filled or dropped by every instance. Forgetting them is a parse error.
  • Defaults are pre-set fields the instance inherits; the cascade flows them to nested modules.
  • Default widget is the custom-element tag the renderer mounts. Every type has its own visual identity.

When to use which:

  • service — a backend service with a maintained team.
  • database — a datastore. Subtypes per family (relational, document, kv_store, …) and per engine (postgres, mongodb, redis, …).
  • message_broker — a deployed broker (Kafka cluster, RabbitMQ, NATS, …).

Use the bare module type when none of these fit — usually rare. When you find yourself wanting “almost service but with three more required fields,” define a project-local subtype (Chapter 16).

arch.c4 is a complete C4 model vocabulary — the four abstraction levels (Context, Containers, Components, Code) minus Code, which is the source itself. Colours follow the canonical C4 palette so diagrams read the way the official notation does.

TypeLevelRequired blanksDefaultsDefault widget
personContextaspect team, aspect domainarch-c4-person
external_personContextarch-c4-external-person
software_systemContextaspect team, big cardarch-module
external_software_systemContextrequired ext.vendor, required ext.contract.urlarch-module (dashed, vendor chip)
containerContaineraspect team, aspect technologyarch-module
componentComponentaspect teamarch-module
enterprise_boundaryBoundarybig dashed cardarch-c4-boundary
system_boundaryBoundarybig dashed cardarch-c4-boundary
container_boundaryBoundarybig dashed cardarch-c4-boundary

container presets the header glyph for the common form factors via subtypes: web_application, single_page_app, mobile_app, api, data_store, message_bus, file_system, serverless_function. Each stays a container for any rule or process step filtering on the parent type.

When to use which:

  • person — a human user, role or persona. external_person sits outside the enterprise in scope (customer, partner) and renders muted + dashed.
  • software_system — the highest abstraction; delivers value to its users. external_software_system is one you don’t own; ext.vendor / ext.contract.url are required so external dependencies always document what they are and where their contract lives.
  • container — a separately deployable/runnable unit (a web app, an API, a database, a queue). NOT a Docker container, though it might be one. aspect technology records the stack the way C4 prints it under the name.
  • component — a grouping of functionality inside a container, behind a well-defined interface. Not separately deployable.
  • boundaries — dashed grouping rectangles that host a subspace of children: enterprise_boundary (people + systems of one org), system_boundary (containers of one system), container_boundary (components of one container).

Relationships (“uses”, “sends data to”, …) are not module types — in ArchLang they fall out of process steps and interface connections between these nodes, the same as every other package.

C4’s supplementary deployment diagram maps the logical containers onto real infrastructure. arch.c4 ships it too, rendered slate-grey to set the physical layer apart from the blue logical elements:

TypeRequired blanksDefaultsDefault widget
deployment_nodeaspect team, aspect technology, big cardarch-module
infrastructure_nodeaspect team, aspect technologyarch-module
container_instance(subtype of container) aspect instances chiparch-module
  • deployment_nodewhere software runs. Nests freely (cloud → region → cluster → host) and hosts a subspace. Flavours preset the glyph: cloud_platform, region, cluster, host, execution_environment, device.
  • infrastructure_node — supporting network kit that isn’t a container: load_balancer, firewall, dns, cdn, gateway.
  • container_instance — a deployed copy of a container; set aspect instances for a replica count (“x3”). aspect technology is the line C4 prints under each node’s name.

arch.c4 is self-contained — it covers the full C4 model (Context, Container, Component, Boundaries, Deployment) on its own. Use it when you want to stay in C4’s fixed vocabulary rather than ArchLang’s open-ended nesting and aspects.

Wire-level interface types, grouped by transport:

GroupTypes
HTTPhttp_get, http_post, http_put, http_patch, http_delete, http_head, http_options, webhook, sse
RESTrest_list, rest_create, rest_read, rest_update, rest_delete (+ rest_crud surface)
gRPCgrpc_unary, grpc_server_stream, grpc_client_stream, grpc_bidi_stream
GraphQLgraphql_query, graphql_mutation, graphql_subscription
WebSocketwebsocket
Messagingkafka, amqp, nats, mqtt, redis_pubsub
Data accessdb_read, db_write

Edge-styling convention: sync request/response types render solid; async / streaming / pub-sub types render dashed. Streaming variants (grpc_server_stream, webhook, sse, graphql_subscription) override their solid parents to render dashed.

The async/event-shaped interfaces (anything dashed — kafka, amqp, nats, mqtt, redis_pubsub, webhook, sse, graphql_subscription, grpc_*_stream) are how you model events. An event is just one of these async interfaces reached by an ordinary > process edge — there is no subscribes: field or separate event construct. The dashed type is what marks the call async; the edge is the subscription.

The same architecture written two ways:

Bare:

module Payments {
aspect team: "Payments"
interface authorize
interface orderEvents
}
module Shipping {
aspect team: "Fulfillment"
interface createShipment
}
process Fulfilment {
Shipping > Payments.orderEvents // the edge subscribes Shipping to the event
}

Stdlib:

service Payments {
aspect team: "Payments"
aspect { domain: "Payments" }
rest_create authorize
kafka orderEvents
}
service Shipping {
aspect team: "Fulfillment"
aspect { domain: "Fulfillment" }
rest_create createShipment
}
process Fulfilment {
Shipping > Payments.orderEvents // async edge — the kafka type renders it dashed
}

The structure is identical — two modules, three interfaces, one process edge that subscribes Shipping to Payments’ event. The stdlib version adds:

  • service widgets (specific visual identity).
  • rest_create / kafka interface types (sync solid vs async dashed edge styling).
  • The domain aspect (now required because the type demands it).

The bare version is valid; the stdlib version is what real projects ship. The translation is mechanical.

A few scenarios where bare types — or your own library — make sense:

  • Learning the language. Chapters 2-10 used bare types for exactly this reason — fewer moving parts to hold in your head.
  • Tiny scratch projects without a manifest. Anonymous packages can’t use anything, so bare types are all you get. (Single-file drafts are first-class — see Chapter 12.)
  • Rolling your own library. Libraries are cheap to write. A large org often wants its own — usually not for widgets (stdlib widgets are reusable) but for org-specific semantics: ownership/maintainer requirements, department relationships, a house notation. Build one from the ground up, or extend stdlib types rather than replace them.
  • Building your own toolchain. If you’re integrating ArchLang into a system that already defines its own ontology, you may want to bypass the stdlib’s vocabulary entirely.

The stdlib is the convenient default, not a requirement — most projects lean on it, swap pieces as they outgrow them, and extend it where house rules demand.

Chapter 12: Packages → — the manifest in depth: dependencies, version, exporting types.