Architecture Documentation


The Core Problem

Why architecture docs fail:

Problem Why it happens
Too much too early Spec written before code — outdated by first sprint
No time during development Devs skip it under pressure, never catch up
No ownership Nobody is explicitly responsible → everyone assumes someone else does it
Unclear scope “What actually goes in there?” — no shared answer

Solution framing: Don’t ask “how do we document everything?” — ask “what and how do we document?” Less is more. Document decisions and context, not implementation details the code already tells you.


Docs-as-Code

Architecture docs live in the source repository, versioned with the code.

Why:

  • Docs stay in sync with the code they describe
  • Same review process (PRs, diffs) as code
  • No separate wiki that rots
  • Diagrams are text → diffable, branchable, mergeable

Tooling for textual diagrams:

  • PlantUML — UML diagrams from text (sequence, class, component)
  • ContextMapper — DDD-specific diagrams from CML DSL (see below)
  • Structurizr — C4 model diagrams from DSL (see below)

Template: Arc42

Site: https://arc42.org/overview

Structured template with 12 sections. Use what fits — skip the rest.

# Section What goes here
1 Introduction & Goals Top 3–5 quality goals, stakeholder table with expectations
2 Constraints Legal, organizational, technical limits that restrict design choices
3 Context & Scope System boundary diagram — what’s inside, what’s outside, who talks to what
4 Solution Strategy Key architectural decisions in 1 page — technology choices, patterns selected
5 Building Block View Static decomposition — white/black box hierarchy of components
6 Runtime View How blocks interact at runtime — sequence diagrams for key scenarios
7 Deployment View Which software runs where — servers, containers, cloud regions
8 Crosscutting Concepts Things that apply everywhere: logging, auth, error handling patterns
9 Architectural Decisions Links to ADRs (see below)
10 Quality Requirements Non-functional requirements as scenarios — “system serves 1000 RPS with <100ms p99”
11 Risks & Technical Debt Known problems, shortcuts taken, things that might bite you
12 Glossary Domain terms and acronyms — especially where business and tech language differ

Practical tip: Section 3 (context diagram) and Section 4 (solution strategy) deliver the most value for the least effort. Start there.


ADRs – Architecture Decision Records

Source: https://www.heise.de/hintergrund/Gut-dokumentiert-Architecture-Decision-Records-4664988.html

Concept by Michael Nygard (2011). One short document per significant architectural decision. Captures why something was decided — not just what.

Structure

# ADR-0042: Use PostgreSQL for primary storage

## Status
Accepted

## Context
We need a relational store. Team has strong SQL expertise. 
MongoDB was evaluated but schema flexibility not needed here.

## Decision
Use PostgreSQL 15. No ORM — plain SQL via jOOQ.

## Consequences
+ ACID guarantees, mature tooling, team knows it
+ jOOQ gives type-safe queries without Hibernate magic
- Schema migrations need careful management (Flyway)
- No easy horizontal write scaling if we hit 10M+ rows/day

The 5 questions a good ADR answers

Question Maps to
When? Date / Status history
Who? Author / team
Why? Context section
What for? Consequences
What? Decision section

Status lifecycle

proposedaccepteddeprecatedsuperseded by ADR-XXXX

Where to store ADRs

docs/
  adr/
    0001-use-postgresql.md
    0002-no-orm-use-jooq.md
    0003-event-sourcing-for-orders.md

Tooling: adr-tools (Bash) handles numbering and status updates automatically. But plain numbered Markdown files work fine.

Key insight: One decision = one ADR. If an ADR contains multiple choices, split it.


ContextMapper

Site: https://contextmapper.org/

DSL for Strategic DDD — models Bounded Contexts and their relationships in code (CML = Context Mapping Language, Xtext-based).

What it generates from a CML model

  • Graphical Context Maps
  • PlantUML component/class diagrams
  • MDSL (micro-)service contracts
  • Spring Boot / JHipster project stubs (via Freemarker templates)

Example CML

ContextMap InsuranceContextMap {
  type = SYSTEM_LANDSCAPE
  state = TO_BE

  contains CustomerManagement, PolicyManagement, ClaimsContext

  CustomerManagement [D, ACL] <- PolicyManagement [U, OHS, PL]
  PolicyManagement [U] -> ClaimsContext [D, CF]
}

BoundedContext CustomerManagement {
  Aggregate Customers {
    Entity Customer {
      aggregateRoot
      String customerId
      String name
      List<Address> addresses
    }
  }
}

Relationship patterns (from DDD): U = Upstream, D = Downstream, ACL = Anti-Corruption Layer, OHS = Open Host Service, PL = Published Language, CF = Conformist.

ContextMapper can also reverse-engineer existing Java codebases into a CML model via its discovery library — useful for documenting legacy systems.


Structurizr + C4 Model

Docs: https://docs.structurizr.com/

Created by Simon Brown (also creator of C4). Single DSL model → multiple diagram levels auto-generated.

The C4 Levels

Level Audience Shows
L1: System Context Everyone incl. non-technical Your system + external users & systems
L2: Container Developers, architects Apps, databases, services inside your system
L3: Component Developers Internals of one container
L4: Code Developers Classes, functions (usually auto-generated from IDE)

Structurizr DSL Example

workspace "Online Shop" {
  model {
    customer = person "Customer"
    shop = softwareSystem "Online Shop" {
      webapp = container "Web App" "React SPA" "TypeScript/React"
      api    = container "API"      "REST API"  "Spring Boot"
      db     = container "Database" "Orders DB" "PostgreSQL" { tags "Database" }
    }
    payments = softwareSystem "Payment Provider" { tags "External" }

    customer -> webapp "Browses and orders"
    webapp   -> api    "API calls" "HTTPS/JSON"
    api      -> db     "Reads/writes"
    api      -> payments "Charges card" "HTTPS"
  }

  views {
    systemContext shop "SystemContext" { include * }
    container shop "Containers" { include * }
  }
}

One model definition → generates System Context and Container diagrams. Add component blocks → generates Component diagrams too.

Why it beats draw.io: The model is the single source of truth. Change a relationship once — all affected diagrams update. No stale diagrams.


Architecture-Code-Gap: jQAssistant + Structurizr

Tool: structurizr-jqassistant plugin

jQAssistant can:

  1. Parse your source code into a Neo4j graph
  2. Import your Structurizr model
  3. Compare them — flag where code diverges from documented architecture

This runs in CI. If a developer adds a dependency between components that the architecture says shouldn’t exist, the build fails.

# Example jQAssistant rule (Cypher query)
- id: architecture:NoDirectDBAccessFromAPI
  description: "Only Repository classes may access the database"
  cypher: |
    MATCH (c:Class)-[:CALLS]->(db:Class {name: "DataSource"})
    WHERE NOT c.name ENDS WITH "Repository"
    RETURN c.fqn AS violatingClass
  severity: MAJOR

Combined workflow:

Structurizr DSL (architecture intent)
        ↓
jQAssistant imports model + scans source code
        ↓
CI runs Cypher rules against the combined graph
        ↓
Build fails on architecture violations

This closes the architecture-code gap automatically — the documented architecture becomes an enforced constraint, not a suggestion.


Summary: Minimal Viable Architecture Doc Stack

Need Tool
Overall structure Arc42 (pick 3–5 sections max)
Decision history ADRs in docs/adr/
Context + container diagrams Structurizr DSL in repo
DDD context mapping ContextMapper CML
Enforcement in CI jQAssistant + Structurizr plugin