Die Kombination aus ArchUnit und einem PlantUML-Export für Dependency-Graphs bringt einen direkten Zusammenhang zwischen implementierter Softwarearchitektur und ihrer visuellen Darstellung. Dadurch entsteht eine sogenannte Living Architecture, bei der die Architektur nicht mehr als statisches Dokument existiert, sondern kontinuierlich aus dem tatsächlichen Codebestand abgeleitet wird.
Das reduziert die Gefahr veralteter oder inkonsistenter Architekturdiagramme erheblich, da jede Änderung im Code automatisch in der Struktur sichtbar wird.

Ein weiterer Vorteil liegt in der automatisierten Validierung der Architekturregeln durch ArchUnit.
Fehlende Schichttrennung, unerlaubte Abhängigkeiten oder zyklische Beziehungen werden frühzeitig erkannt und können bereits im Build-Prozess verhindert werden. Der PlantUML-Export ergänzt diese Regelprüfung um eine visuelle Ebene, die insbesondere bei komplexeren Systemen die Verständlichkeit der Architektur deutlich verbessert.
Zusätzlich entsteht eine hohe Transparenz über Paket- und Modulabhängigkeiten. Entwickler können auf einen Blick erkennen, wie sich Komponenten gegenseitig beeinflussen und wo potenzielle strukturelle Probleme liegen. Dies unterstützt sowohl die Wartbarkeit als auch die Weiterentwicklung des Systems, da Architekturentscheidungen nachvollziehbar und überprüfbar bleiben. Insgesamt führt dieser Ansatz zu einer stärker kontrollierten, konsistenten und langfristig stabilen Softwarearchitektur.
Automatisch generiert aus Code mal mit Archunit, erzeugt z.B. die obige UML Datei (puml mit Eclipse-Plugin für autom. Rendern) mit folgenden Vorteilen:
– echte Paketabhängigkeiten
– keine manuelle Pflege von UML
– immer aktuell mit jedem Build
Gute Architekturqualität (wichtig)
Dieses Setup erzwingt:
– Dynamische Architekturvisualisierung
– keine veralteten Diagramme mehr
– „Living Architecture“
– CI-fähig
Hier der Java Code im Testpackage am Beispiel der Mammutbäume:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
package de.wenzlaff.taxonomie.arch; import java.nio.file.Files; import java.nio.file.Path; import java.util.Set; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchRule; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; /** * Erzeugen einer target/taxonomy-archirecture.puml Datei. * * @author Thomas Wenzlaff * */ @AnalyzeClasses(packages = "de.wenzlaff.taxonomie") public class ArchitecturePlantUmlExportTest { private final JavaClasses classes = new ClassFileImporter().importPackages("de.wenzlaff.taxonomie"); // ========================================================= // 1. Architekturregeln (optional zusätzlich) // ========================================================= @ArchTest static final ArchRule model_is_isolated = noClasses().that().resideInAPackage("..model..").should() .dependOnClassesThat().resideInAPackage("..service.."); // ========================================================= // 2. PlantUML Export Test // ========================================================= @Test void exportDependencyGraphAsPlantUML() throws Exception { StringBuilder uml = new StringBuilder(); uml.append("@startuml\n"); uml.append("title Taxonomie Architektur (ArchUnit Export)\n\n"); // ===================================================== // Knoten (Pakete) // ===================================================== uml.append("package model {}\n"); uml.append("package service {}\n"); uml.append("package data {}\n"); uml.append("package arch {}\n\n"); // ===================================================== // Abhängigkeiten extrahieren // ===================================================== Set<String> edges = classes.stream().flatMap(c -> c.getDirectDependenciesFromSelf().stream()) .filter(dep -> dep.getTargetClass().getPackageName().startsWith("de.wenzlaff.taxonomie")).map(dep -> { String from = dep.getOriginClass().getPackageName(); String to = dep.getTargetClass().getPackageName(); return from + " --> " + to; }).collect(Collectors.toSet()); // ===================================================== // Kanten schreiben // ===================================================== for (String edge : edges) { String cleaned = edge.replace("de.wenzlaff.taxonomie.", "").replace("..", ""); uml.append(cleaned).append("\n"); } uml.append("\n@enduml"); // ===================================================== // Datei schreiben // ===================================================== Path output = Path.of("target/taxonomy-architecture.puml"); Files.createDirectories(output.getParent()); Files.writeString(output, uml.toString()); } } |
