Dieser Post setzt den siebten Teil fort. Architekturprinzipien schreiben sich leicht in ADRs. Schwieriger ist es, sicherzustellen dass der Code — und die Architektur — diese Prinzipien über Monate und Teams hinweg tatsächlich einhält. Bausteinsicht löst das mit maschinenprüfbaren Constraints.
Was ist Architektur-Linting?
Linting kennt man aus der Entwicklung: ESLint prüft JavaScript-Code auf Stilregeln und Fehler, go vet prüft Go-Code auf Antipatterns.
Architektur-Linting macht dasselbe für das Architekturmodell:
Keine direkte Datenbankverbindung aus der UI-Schicht
Alle Systeme brauchen eine Beschreibung
Maximale Verschachtelungstiefe von 3 Ebenen
Keine zirkulären Abhängigkeiten
Nur freigegebene Technologien
Diese Regeln sind im Modell als constraints definiert und werden mit bausteinsicht lint geprüft.
Constraints im Modell definieren
Constraints gehören in den constraints-Array in architecture.jsonc (→ Teil 3).
Jeder Constraint braucht id, description und rule — plus regelspezifische Felder.
no-relationship
Verhindert direkte Verbindungen zwischen bestimmten Element-Typen:
"constraints": [
{
"id": "NO-UI-DB",
"description": "UI-Komponenten dürfen nicht direkt auf die Datenbank zugreifen",
"rule": "no-relationship",
"from-kind": "component",
"to-kind": "database"
}
]allowed-relationship
Definiert positiv, welche Element-Typen sich mit einem bestimmten Typ verbinden dürfen:
{
"id": "DB-ACCESS-ONLY-SERVICES",
"description": "Nur Services und Repositories dürfen auf Datenbanken zugreifen",
"rule": "allowed-relationship",
"to-kind": "database",
"from-kinds": ["service", "repository"]
}required-field
Erzwingt dass ein Feld bei allen Elementen eines bestimmten Typs gesetzt ist:
{
"id": "SYSTEM-NEEDS-DESCRIPTION",
"description": "Alle Systeme müssen eine Beschreibung haben",
"rule": "required-field",
"element-kind": "system",
"field": "description"
},
{
"id": "CONTAINER-NEEDS-TECHNOLOGY",
"description": "Alle Container müssen eine Technologie angeben",
"rule": "required-field",
"element-kind": "container",
"field": "technology"
}Unterstützte Felder: description, technology, title.
max-depth
Begrenzt die Verschachtelungstiefe des Modells:
{
"id": "MAX-NESTING-3",
"description": "Maximale Verschachtelungstiefe ist 3 (System → Container → Component)",
"rule": "max-depth",
"max": 3
}no-circular-dependency
Erkennt Zyklen im Abhängigkeitsgraphen mittels Tiefensuche:
{
"id": "NO-CYCLES",
"description": "Keine zirkulären Abhängigkeiten zwischen Elementen",
"rule": "no-circular-dependency"
}technology-allowed
Erzwingt einen genehmigten Technologie-Stack:
{
"id": "APPROVED-BACKEND-STACK",
"description": "Backend-Container dürfen nur genehmigte Technologien verwenden",
"rule": "technology-allowed",
"element-kind": "container",
"technologies": ["Go", "Rust", "Python", "PostgreSQL", "Redis"]
}bausteinsicht lint
bausteinsicht lintAusgabe bei Verstößen:
VIOLATION [NO-UI-DB]: UI-Komponenten dürfen nicht direkt auf die Datenbank zugreifen: component kind must not relate to database kind - shop.frontend → shop.db VIOLATION [CONTAINER-NEEDS-TECHNOLOGY]: Alle Container müssen eine Technologie angeben: all container elements must have "technology" set - shop.legacy: missing technology lint: 2 violation(s) found
Ausgabe wenn alle Regeln bestanden:
All constraints passed.
Exit-Code: 0 bei Erfolg, 1 bei Verstößen — damit direkt in CI verwendbar.
JSON-Ausgabe für CI-Auswertung
bausteinsicht lint --format json{
"passed": false,
"total": 2,
"violations": [
{
"constraintId": "NO-UI-DB",
"message": "...",
"elements": ["shop.frontend → shop.db"]
}
]
}bausteinsicht validate
validate ist leichtgewichtiger als lint — es prüft nur die strukturelle Korrektheit des Modells (Schema, Referenzen):
bausteinsicht validatePrüft:
Alle
kind-Werte inmodelsind inspecification.elementsdefiniertAlle
kind-Werte inrelationshipssind inspecification.relationshipsdefiniertAlle
decisions-Referenzen in Elementen existieren inspecification.decisionsAlle
tagsin Elementen sind inspecification.tagsdefiniertcontainer: truefür Elemente mitchildren
validate läuft implizit vor jedem sync — ein invalid Model wird nicht synchronisiert.
bausteinsicht health
health bewertet die Architekturqualität über mehrere Dimensionen hinweg und gibt einen Score von 0–100 (Note A–F):
bausteinsicht healthAusgabe:
Architecture Health Report
==========================
Overall Score: 74.5/100 [B]
Summary: Good architecture documentation with some gaps
Timestamp: 2025-06-11T14:30:22Z
Model Statistics
----------------
Elements: 15
Relationships: 11
Views: 3
Category Scores
---------------
Completeness: 85.0/100 (weight: 40%)
Details: 13/15 elements have descriptions
Conformance: 90.0/100 (weight: 30%)
Details: All constraints passed
Complexity: 60.0/100 (weight: 30%)
Details: High relationship density detected
Findings
--------
Completeness (2 findings):
[WARN] Missing descriptions
shop.legacy, shop.legacy.api: description is empty
Complexity (1 finding):
[INFO] High coupling
shop.api has 7 outgoing relationships — consider splittingKurzansicht für CI-Dashboards:
bausteinsicht health --summary
# → Overall Score: 74.5/100 [B]
# Als JSON
bausteinsicht health --format json --summary
# Report in Datei schreiben
bausteinsicht health --output docs/health-report.txtCI/CD Integration
lint und validate sind direkt für CI gemacht. Beispiel GitHub Actions:
- name: Validate architecture model
run: bausteinsicht validate
- name: Lint architecture constraints
run: bausteinsicht lint
- name: Architecture health check (informational)
run: bausteinsicht health --summary
continue-on-error: true # health bricht nicht den Build, informiert nurlint gibt Exit-Code 1 bei Verstößen zurück — der CI-Build schlägt fehl.
Das ist bewusst so: Architekturregeln sollen genauso verbindlich sein wie Coding-Guidelines. |
Vollständiges Constraint-Set für eine Schichtenarchitektur
"constraints": [
{
"id": "NO-CYCLES",
"description": "Keine zirkulären Abhängigkeiten",
"rule": "no-circular-dependency"
},
{
"id": "NO-UI-TO-DB",
"description": "UI greift nicht direkt auf DB zu",
"rule": "no-relationship",
"from-kind": "frontend",
"to-kind": "database"
},
{
"id": "DB-ONLY-FROM-BACKEND",
"description": "Nur Backend-Services greifen auf DB zu",
"rule": "allowed-relationship",
"to-kind": "database",
"from-kinds": ["service", "repository"]
},
{
"id": "MAX-DEPTH",
"description": "Maximale Tiefe: System → Container → Component",
"rule": "max-depth",
"max": 3
},
{
"id": "SYSTEM-DOCUMENTED",
"description": "Alle Systeme brauchen eine Beschreibung",
"rule": "required-field",
"element-kind": "system",
"field": "description"
},
{
"id": "CONTAINER-TECH",
"description": "Alle Container deklarieren ihre Technologie",
"rule": "required-field",
"element-kind": "container",
"field": "technology"
},
{
"id": "APPROVED-STACK",
"description": "Nur freigegebene Technologien",
"rule": "technology-allowed",
"element-kind": "container",
"technologies": ["Go", "TypeScript", "React", "PostgreSQL", "Redis", "Kafka"]
}
]Beispiel-Modell
Das Beispiel für diesen Teil mit Schichtenarchitektur und Constraints liegt unter teil_8.jsonc.
So sieht das Ergebnis in draw.io aus (bausteinsicht sync):
Das draw.io-File dafür findest du hier: teil_8.drawio
Generierte PNG-Dateien via bausteinsicht export --image-format png:


Generierte PlantUML-Diagramme via bausteinsicht export-diagram:
Was als nächstes kommt
Teil 9: Graph-Analyse — Zyklen und Abhängigkeiten mit
bausteinsicht graphaufdeckenTeil 10: Overlay & Heatmap — Metriken auf Architekturdiagramme legen
Offizielle Dokumentation: User Manual · Tutorial auf doctoolchain.org