ERDs and sequence diagrams as code
Copy valid YAML for typed entities and an ordered login exchange, then review the schema and failure paths that syntax alone cannot verify.
Suppose a change adds a session table and moves session creation later in login. An ERD can show the new foreign key, but it won't show whether the application returns success before the session is stored.
Review the data model and the message order separately. That distinction is useful with a whiteboard or Mermaid as well as YAML: the ERD describes what can relate, while the sequence diagram describes an exchange over time.
Give the data model explicit relationships
This hypothetical schema extends the User entity from our DSL reference with a Session entity. It uses typed rows and an explicit relationship between columns. Copy the whole block as a document; each entity belongs under nodes.
apiVersion: archdocs/v1
title: Users and sessions
nodes:
- id: User
type: erd-entity
rows:
- { rowId: user_id, label: id, valueType: uuid, pk: true }
- { rowId: user_email, label: email, valueType: varchar(255), notNull: true, unique: true }
- id: Session
type: erd-entity
rows:
- { rowId: session_id, label: id, valueType: uuid, pk: true }
- { rowId: session_user, label: user_id, valueType: uuid, fk: true, notNull: true }
relationships:
- from: User.id
to: Session.user_id
cardinality: one-to-many
label: Has sessions
pk, fk, notNull and unique describe the intended constraints. They don't run a migration. Compare them with the actual schema before approving a database change.
The explicit cardinality puts the many end at Session.user_id. The renderer can also derive crow's-foot ends from PK and FK flags when cardinality is omitted. Neither approach establishes deletion behavior or optional participation. If sessions survive user deletion, or an anonymous session may have no user, write that down and reconcile it with the real foreign key and nullability rules.
In any ERD tool, attaching the relationship to the column removes ambiguity about which field is the foreign key. It becomes more useful when an entity has several references to the same table, such as created_by and approved_by.
Keep identity separate from the column label
The relationship endpoints above use unique column labels. If labels repeat inside an entity, the DSL requires NodeId.columnLabel#rowId to disambiguate them. The rowId identifies the row; its label is display text.
Give rows explicit IDs when you expect to reorder them. Without one, the compiler assigns an ID from the row's position. Keep node IDs stable too. Renaming User to Account requires updating relationship endpoints, even if the displayed title was the only thing you meant to change.
For a larger system, keep this schema detail separate from the C4 container view. A box for the identity database tells a different story from a card for each table inside it.
Describe an exchange with ordered steps
This next hypothetical document extends the Login example in our sequence reference. It shows the successful path only. The note marks a condition the API must satisfy before it returns success.
apiVersion: archdocs/v1
title: Login exchange
nodes:
- id: login
type: sequence-diagram
title: Login
participants:
- { id: u, label: User, kind: actor }
- { id: api, label: API, kind: object }
- { id: db, label: Session store, kind: object }
steps:
- { message: "POST /login", from: u, to: api, msgType: sync }
- { message: "Store session", from: api, to: db, msgType: sync }
- { message: "Session stored", from: db, to: api, msgType: return }
- { kind: note, text: "Return success only after storage succeeds", anchor: api }
- { message: "Login accepted", from: api, to: u, msgType: return }
relationships: []
The sequence is self-contained. Its messages belong in steps, not in the document's top-level relationships. Participant IDs connect each message to its sender and receiver. sync and return express the intended exchange; they don't instrument a running application or prove its timing.
Now review the missing path. If storage fails, the successful response shown here must not occur. Add a separate failure sequence or document the failure in a note beside the relevant step. Authentication checks are also outside this small example, so don't treat it as a complete login design.
An async message needs a different review question: what acknowledges receipt, and what confirms completion? Drawing an async arrow alone doesn't tell a caller when it is safe to act on the result. Put that confirmation in the sequence if the caller depends on it.
Review the source and the drawing
Keep stable IDs when editing these documents so a label change doesn't look like removing one node and adding another. Read the YAML diff, then inspect the rendered diagram. The source is better for checking fields; the drawing is better for noticing a relationship attached to the wrong entity or an exchange that's difficult to follow.
Archdocs compiles this YAML into structured elements. Its validator does a dry-run compile, so it checks more than YAML syntax, including ERD endpoint resolution. It doesn't compare the ERD with your database or the sequence with an execution trace. Those checks belong in the review process even if you use another architecture as code tool.
When converting a canvas back to YAML, readable constructs are reconstructed where possible and remaining elements use a raw fallback. That preserves drawing data at the cost of less readable source. Before replacing a maintained YAML document with a decompiled version, inspect the diff for layout coordinates and raw nodes that don't belong in the change you're reviewing.
Architecture docs that stay alive
ArchDocs keeps diagrams and docs in sync with your systems instead of letting them rot. Create a free account to start documenting your architecture.