Draw a model
Draw a model as a dependency tree, matrix or architecture layers with aontu view, and gate the committed figures in CI.
Rendered from
docs/how-to/draw-a-model.md
in the engine repository, where a correction belongs, and where the
test suite executes every example on this page.
A model that says which service depends on which, or which module sits
in which layer, already holds the picture. aontu view draws it from
the evaluated document, so the figure is a fact about the model rather
than a diagram someone kept in step with it.
Here is a system.aon with three services and a dependency relation:
spec: hide({
Service: {
tier: "edge"|"core"
dependsOn?: rel($.spec.Service) & acyclic() & [&: refer($.spec.Service)]
}
})
web: $.spec.Service & { tier:"edge" dependsOn: [path($.api)] }
api: $.spec.Service & { tier:"core" dependsOn: [path($.store)] }
store: $.spec.Service & { tier:"core" }
The tree is the default kind. It derives its roots (a root is a node nothing depends on) and prints nothing else, so a redirect is a golden file:
$ aontu view tree --relation dependsOn system.aon
web
└── api
└── store
The same edges as a dependency-structure matrix, in partition order, mark a cell where the row depends on the column. A perfect lower triangle is the acyclicity proof in the picture’s own shape, and the footer counts the cells above the diagonal:
$ aontu view matrix --relation dependsOn --order partition system.aon
1 2 3
store 1 \ . .
api 2 X \ .
web 3 . X \
# above-diagonal direct cells: 0
Draw the architecture layers
Any field of a node can be the band it belongs to. --group-by names
it, and the band order is derived from the relation rather than
declared, so it follows the model:
$ aontu view layer --relation dependsOn --group-by tier system.aon
+------------------+
| edge web |
+------------------+
| core api store |
+------------------+
# dependsOn: 1 downward, 1 sideways, 0 upward
The footer is the layering rule, counted: web -> api crosses a band
and api -> store stays inside one. An upward edge (one that
points back up the bands) is the violation, and is named under the
figure. --edges all draws the whole relation over the bands
instead, which is what a reader tracing one service’s dependencies
wants.
Get an SVG for a web page
The kinds whose text form is a grid of character cells (tree,
matrix, layer, sets and layers) also render as a standalone
SVG that carries its own style block and takes its colours from CSS
variables, so a page can theme it:
$ aontu view tree --relation dependsOn --as svg --out tree.svg system.aon
$ echo $?
0
The node-link kinds (graph, ladder, poset) render as Mermaid or
DOT, which have renderers of their own.
Gate the figures in CI
A committed figure that nobody re-draws is a stale picture. Write the
figures down as data, in a views.aon that includes the model:
@"aontu:view"
@"./system.aon"
views: { &: $.aontu.View.Figure } & {
tree: { kind:tree relation:dependsOn out:"tree.txt" }
matrix: { kind:matrix relation:dependsOn order:partition out:"matrix.txt" }
layers: { kind:layer relation:dependsOn groupBy:tier as:svg out:"layers.svg" }
}
views is your key, not one the engine knows, so --views says where
to look. Each declaration’s keys are the view options (the flags
without the dashes) and @"aontu:view" is the schema for one, so a
misspelled option or a kind that is not a kind is refused when the
document is evaluated.
Draw them all from one evaluation, then gate them:
$ aontu view --views '$.views' views.aon
$ aontu view --views '$.views' --check views.aon
$ echo $?
0
--check writes nothing and exits 1 naming every figure that
differs. Each out is resolved against the view document’s own
directory, so the gate passes from any working directory; the
directory it names must already exist, as it must for --out. Nothing
is written unless every figure rendered: N figures of one model are
only meaningful together.
What the figure could not draw
Every run reports its losses on stderr, so a figure written to a file
still tells the reader what it left out: a hide()-marked edge, a
--group-by field a node does not have, a cycle that blocks the
partition order, columns elided by --max-cols. --strict turns any
of that into exit 1, and --max-rows refuses a figure too big to
read rather than truncating it.
Related
aontu view. Every kind, every option, the loss codes and the exit codes.- Query a path. Read one node instead of drawing the whole shape.
- Explain a value. Why one value is what it is,
which the
ladderkind draws.