Architecture diagrams famously go stale. What causes drift between views and the real system, and what mechanisms keep multiple views consistent with each other and with reality?
answer
- Two drifts: view↔reality and view↔view
- Churn gradient: context yearly → classes hourly
- One textual model in repo → many rendered views
- Fitness functions = executable development view
- Owner + update trigger; ADRs carry the why
basics
~20 sDiagrams drift because they are separate hand-maintained artefacts with no owner and no update trigger, while code changes daily. Fixes: keep few, high-level views; store the model as code next to the source; generate volatile detail; check correspondences automatically.
solid answer
~50 sDrift has structural causes: hand-drawn pictures live outside the change process, low-level views churn fastest, several views duplicate the same facts so they diverge from each other as well as from the code, and nobody owns an update trigger. Countermeasures, roughly in order of leverage: (1) **choose the right abstraction level** — context/container views change monthly, class diagrams change hourly, so maintain high and generate low; (2) **model as code** — a single textual model (Structurizr DSL, PlantUML/C4, Mermaid, Likec4) in the same repository, rendering multiple views from one source so cross-view facts cannot disagree by construction, and reviewed in the same pull request as the change; (3) **automated correspondence checks** — architecture fitness functions such as ArchUnit or dependency-cruiser assert the documented module rules against the real code and fail the build; (4) **derive from runtime/infrastructure** — generate deployment views from IaC and service maps from tracing; (5) **ownership and triggers** — each view has an owner and a named event that forces an update.
code
text · 8 lines// Fitness function: the development view's layering rule, executable
// (ArchUnit-style, JVM — equivalents: dependency-cruiser, import-linter)
rule = classes().that().resideInPackage("..domain..")
.should().onlyDependOnClassesThat()
.resideInAnyPackage("..domain..", "java..")
// Runs in CI. If someone imports the web layer into domain,
// the build fails — the documented rule cannot silently rot.go deeper
Say diagrams go stale because they are updated by hand and separately from code, and that keeping fewer, higher-level diagrams close to the code helps.
Name diagrams-as-code tooling, the churn gradient by abstraction level, and generating low-level detail instead of drawing it.
Add fitness functions enforcing documented rules in CI, deriving deployment views from IaC and interaction views from tracing, explicit correspondence rules, and owner/trigger discipline.
Frame it as documentation economics and governance: which artefacts justify their maintenance cost, intended-vs-actual model diffing as a standing review input, organisation-wide house notation and tooling, and ADRs as the immutable rationale record that survives structural change.
## What "drift" actually is Two distinct failures get lumped together: 1. **View-to-reality drift** — the diagram says something the running system does not do. A box was renamed, a queue was added, a second region went live, a service was merged. 2. **View-to-view drift** — two diagrams of the same system contradict each other. The container diagram shows a message broker that the deployment diagram never places on a node; the component view names a module that the development view's dependency rules forbid. The second is easy to miss and just as damaging, because it destroys trust in the whole set. ## Why drift is the default outcome - **Diagrams sit outside the change workflow.** Code changes go through a pull request with review and CI. A diagram in a wiki or a drawing tool has no such gate, so updating it is voluntary work with no enforcement. - **Redundancy without a single source.** Views intentionally overlap (that overlap is what scenarios exploit to catch design errors), but if each view is authored independently, every overlapping fact is a place they can disagree. - **The churn gradient.** Rate of change rises sharply as abstraction falls: system context changes maybe yearly, containers monthly, components weekly, classes hourly. Hand-maintaining anything below component level is a losing race. - **No owner, no trigger.** "Someone should update the diagram" is not a process. Without a named owner and an event that forces the update, entropy wins. - **Binary formats.** A PNG or a proprietary drawing file cannot be diffed or reviewed, so changes to it are invisible in review. - **Documentation written once, at project start**, when the architecture is least known and most likely to change. ## Countermeasures, from highest leverage down ### 1. Fewer views, higher up The cheapest way to stop drift is to not create the artefacts that drift fastest. Maintain context/container-level views by hand; treat class-level detail as something you **generate on demand** from the IDE, exactly as the C4 model advises for its level 4. Fewer, accurate views beat a complete-but-stale set, because a wrong diagram causes worse decisions than a missing one. ### 2. Model as code, one model, many views Keep a **single textual model** in version control in the same repository as the code: Structurizr DSL, PlantUML with C4 macros, Mermaid's C4 support, Likec4, or a similar tool. Consequences: - Multiple views (context, container, component, deployment, dynamic) are **rendered from one model**, so shared facts cannot disagree between views — view-to-view drift is eliminated by construction. - The model is plain text, so it **diffs and reviews** like code. - Updating it can be made part of the same pull request as the change, bringing documentation inside the existing enforcement gate. Some teams add a CI reminder when specified paths change without the model changing. ### 3. Fitness functions — check the documented rules against the real code Where a view expresses a *rule* (layering, allowed dependencies, module boundaries), encode the rule as an automated test that fails the build on violation: ArchUnit or Spring Modulith verification in JVM projects, dependency-cruiser or ESLint boundary rules in JavaScript/TypeScript, import-linter in Python, or a custom check anywhere. This converts a development view from a picture into an **executable constraint** — the one form of documentation that cannot silently go stale. This is the "architecture fitness function" idea from evolutionary architecture. ### 4. Derive views from authoritative sources - **Deployment view** from infrastructure-as-code (Terraform, Kubernetes manifests) or the cloud provider's inventory. - **Runtime interaction view** from distributed tracing / service-mesh topology, which shows the calls that actually happen rather than the ones you believe happen — an excellent way to discover undocumented dependencies. - **Component/module view** from static analysis of the real dependency graph. Generated views are always current, but they are also *unfiltered*: they show what is, not what was intended. Keep an intended model alongside and treat the difference as the finding. ### 5. Explicit correspondence rules ISO/IEC/IEEE 42010 provides the concept: state the relations that must hold across views ("every container appears on at least one deployment node", "every component belongs to exactly one container", "every external system in the context view has at least one connection in the container view"). With a single model, many of these are checkable automatically; without one, they become a short review checklist. ### 6. Ownership, triggers and review cadence Each maintained view gets a named owner and a trigger list: adding or removing a container, adding a node type or region, adding an external integration, changing a protocol. Add a low-frequency calendar review (quarterly) as a backstop, and treat significant incidents as a forced review — incidents reliably reveal the parts of the map that were wrong. ### 7. Record decisions separately and immutably Structure drifts; rationale does not have to. **ADRs** are short, dated and append-only: superseded decisions are marked superseded rather than edited. So even when a diagram is behind, the decision log still explains why the system looks the way it does. ## Trade-offs worth naming - **Generated vs curated.** Generated views never lie but are noisy and unopinionated; curated views communicate intent but rot. The mature answer is both: curate the intended model, generate the actual one, and diff them. - **Automation cost.** Fitness functions and model tooling are real engineering work with their own maintenance; on a small, stable system a quarterly manual review may be cheaper. - **False confidence.** A diagram generated from code proves what the code does, not that it matches the architecture you intended — that is exactly why the intended model must still exist. - **Over-constraining.** Fitness functions that encode rules nobody agreed to become a source of build failures and get disabled; encode only the boundaries you would actually defend in review. ## The short interview answer Drift is a process problem, not a drawing problem. Reduce the surface (fewer, higher-level views), move the model inside the change-review gate (diagrams as code in the repo), make the rules executable (fitness functions), generate what can be generated, and give every remaining artefact an owner and a trigger.
- How does keeping the architecture model as text in the repository actually prevent drift?It brings documentation inside the mechanism that already enforces change discipline: the model diffs and is reviewed in the same pull request as the code, so a reviewer sees a structural change with no model change. It also lets several views be rendered from one model, which removes contradictions between views by construction.
- What is an architecture fitness function and how does it relate to views?An automated, objective check that the system still exhibits a desired architectural characteristic — for example an ArchUnit or dependency-cruiser test asserting that the domain layer never imports the web layer. It turns a rule expressed in the development view into an executable constraint that fails the build, so that part of the documentation cannot silently go stale.
- If you generate a diagram from runtime tracing and it disagrees with your hand-drawn container diagram, which one is wrong?Neither automatically — the generated one shows what the system does, the curated one shows what you intended. The disagreement is the finding: either an undocumented dependency crept in (fix the system or the diagram) or the intended design was never implemented. Investigate rather than blindly overwriting the intent.
A hand-drawn map of a growing city goes stale the moment a road opens; a map generated from the road registry is always current but shows every alley. Cities keep both: the official plan (intent) and the survey (reality), and the difference between them is the planning department's agenda.
saying these in an interview costs you the question
- Treating drift as a discipline problem solved by asking people to remember
- Committing exported PNGs with no reviewable text source
- Hand-maintaining class-level diagrams
- Assuming a generated diagram proves the architecture is as intended
- Keeping several independently authored diagrams that duplicate the same facts
- No owner and no update trigger for any diagram
- Relying on diagrams to carry rationale instead of keeping an ADR log