Dependency-graph reports
This family judges nothing. It describes a report — what to draw, how far, under what labels — and then hands it back as data or as a document:
snapshot, err := archunit.ProjectGraph(nil).
FocusOn("internal/api/**", 1).
CollapseToFolderDepth(2).
Titled("what the api layer touches").
Snapshot()
archunit.ProjectGraph is the entry point and archunit.DependencyGraph the other name the family gives it.
There is no mood, no predicate and no violation type here, and that is the grammar rather than an omission: a
report is not a rule, so there is nothing for it to disagree with.
The default report is one node per file of the project’s own code, with one arrow per dependency between two of them.
The nine modifiers
Every one is optional, chainable and order-independent.
| Modifier | Does |
|---|---|
IncludingExternalDependencies |
adds the code outside the project — the standard library and the modules it depends on |
IncludingSelfDependencies |
keeps a node’s dependency on itself, which is dropped by default |
FocusOn |
narrows to the files a pattern names plus their neighborhood, that many hops out in both directions |
ReachableFrom |
narrows to those files and everything they depend on, transitively |
DependentsOf |
narrows to those files and everything that depends on them, transitively |
CollapseToFolderDepth |
draws each folder at that depth as one node |
CollapseByPattern |
draws every node a pattern names as one node under a label you choose |
Titled |
says what the report is called |
WithCheckOptions |
says how the project is read — test files, ignored import kinds, where it is |
IncludingExternalDependencies and IncludingSelfDependencies are the two verbs that widen the report; the
three that narrow it are three different questions. FocusOn is what is around this code, and depth 0 is the named files alone. ReachableFrom
follows the arrows forwards with no bound — what does this pull in, which is what a binary actually reaches.
DependentsOf follows them backwards — who would notice if this changed, which is the answer worth having
before deleting a folder.
snapshot, err := archunit.ProjectGraph(nil).
DependentsOf("internal/db/**").
Titled("who would notice if the database changed").
Snapshot()
Patterns here are matched against the whole identifier, so internal/api/** is that folder and everything
below it. They match identifiers and never the labels a collapse draws, which is what keeps focusing and
collapsing in one chain order-independent: the neighbors of the files named, drawn as folders.
The four modifiers that take a pattern each take Except, which qualifies the one the chain wrote most
recently — the one word in this family that is not order-independent, because an exclusion belongs to the
clause it was typed in:
snapshot, err := archunit.ProjectGraph(nil).
IncludingExternalDependencies().
FocusOn("app/**", 1).
Except("**/generated/**").
CollapseByPattern("api", "internal/api/**").
CollapseByPattern("third party", "**").
Snapshot()
CollapseByPattern is the modifier for a diagram whose boxes are the architecture rather than the directory
tree: two folders that are one component, every dependency module as a single third party node, a legacy
corner as one box nobody wants to look inside. The label is asked for rather than derived, because a box has to
be called something and internal/{api,web}/** is not a name anybody wants to read — and giving those groups
the same names as a layer policy’s layers is what makes a report and a rule describe one
architecture. Groups are asked before CollapseToFolderDepth, so the two compose.
The thirteen terminals
Snapshot hands the report back as data, and the other twelve hand it back as a document — six formats, each
as a string or as a file:
| Format | As a string | As a file |
|---|---|---|
| Graphviz | ToDot |
ExportAsDot |
| Mermaid | ToMermaid |
ExportAsMermaid |
| D2 | ToD2 |
ExportAsD2 |
| Comma-separated values | ToCSV |
ExportAsCSV |
| JSON | ToJSON |
ExportAsJSON |
| A self-contained web page | ToHTML |
ExportAsHTML |
err := archunit.ProjectGraph(nil).
CollapseToFolderDepth(2).
Titled("the modules of this project").
ExportAsHTML("build/architecture.html")
Every one of the twelve is Snapshot followed by one rendering function, which is why a new format is one
function this chain does not have to know about, and a modifier added here is understood by every format the
day it lands. A query that described a report with no node in it is ErrEmptySnapshot rather than an empty
diagram, for the same reason zero matches is a violation everywhere else.
Note that the terminals of this family take no options bag — the six exporters take the path they write to
and nothing else, and the other seven take no argument at all. The check options are a modifier here,
WithCheckOptions, because a report has no Check to pass them to.
Reading a snapshot
A GraphSnapshot is immutable and ordered: nodes by label, dependencies by source and then target.
| Method | Hands back |
|---|---|
Nodes |
the GraphNode values that survived the query |
Edges |
the GraphEdge values drawn between them |
Summary |
the GraphSummary: the counts |
Title |
what the report is called |
Empty |
whether it holds no node at all |
A GraphNode has a Label — a file identifier, an import path, or the group a collapse merged it into — and
IsExternal, which says whether it is somebody else’s code. A GraphEdge has its SourceLabel and
TargetLabel, an IsSelfDependency flag, an IsExternal flag, the import kinds behind it, and a Count:
how many of the project’s raw dependencies this one arrow stands for. That count is what makes a collapsed
diagram honest — forty files merged onto two folders is one arrow, and an arrow that does not say 312
dependencies invites the reader to think the two folders are barely coupled.
A GraphSummary is a snapshot in numbers, and every field is one a caller legitimately wants alone:
Nodes, Edges, Dependencies — the raw dependencies behind those edges, which does not shrink when a
collapse merges arrows — ExternalNodes and ExternalEdges. A test asserting that this project depends on
nothing outside it reads one of them.
Next
The graph is extracted once per project and memoised, because a suite asks about one project many times —
which is why running a rule has a word to say about archunit.ClearGraphCache.