{"id":23250,"date":"2026-06-07T05:05:17","date_gmt":"2026-06-07T03:05:17","guid":{"rendered":"http:\/\/blog.wenzlaff.de\/?p=23250"},"modified":"2026-06-06T19:35:44","modified_gmt":"2026-06-06T17:35:44","slug":"automatisch-uml-generierung-plantuml-format-mit-archunit","status":"publish","type":"post","link":"http:\/\/blog.wenzlaff.de\/?p=23250","title":{"rendered":"Automatisch UML Generierung (plantUML-Format) mit ArchUnit"},"content":{"rendered":"<p>Die Kombination aus <a href=\"https:\/\/www.archunit.org\/userguide\/html\/000_Index.html\" target=\"_blank\">ArchUnit<\/a> und einem PlantUML-Export f\u00fcr Dependency-Graphs bringt einen direkten Zusammenhang zwischen implementierter Softwarearchitektur und ihrer visuellen Darstellung. Dadurch entsteht eine sogenannte <em>Living Architecture<\/em>, bei der die Architektur nicht mehr als statisches Dokument existiert, sondern kontinuierlich aus dem tats\u00e4chlichen Codebestand abgeleitet wird. <\/p>\n<p>Das reduziert die Gefahr veralteter oder inkonsistenter Architekturdiagramme erheblich, da jede \u00c4nderung im Code automatisch in der Struktur sichtbar wird.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2026\/06\/puml-generieren.jpeg\" alt=\"\" width=\"2480\" height=\"978\" \/><\/p>\n<p>Ein weiterer Vorteil liegt in der <strong>automatisierten Validierung der Architekturregeln<\/strong> durch <a href=\"https:\/\/www.archunit.org\/userguide\/html\/000_Index.html\" target=\"_blank\">ArchUnit<\/a>. <\/p>\n<p>Fehlende Schichttrennung, unerlaubte Abh\u00e4ngigkeiten oder zyklische Beziehungen werden fr\u00fchzeitig erkannt und k\u00f6nnen bereits im Build-Prozess verhindert werden. Der PlantUML-Export erg\u00e4nzt diese Regelpr\u00fcfung um eine visuelle Ebene, die insbesondere bei komplexeren Systemen die Verst\u00e4ndlichkeit der Architektur deutlich verbessert.<\/p>\n<p>Zus\u00e4tzlich entsteht eine hohe Transparenz \u00fcber Paket- und Modulabh\u00e4ngigkeiten. Entwickler k\u00f6nnen auf einen Blick erkennen, wie sich Komponenten gegenseitig beeinflussen und wo potenzielle strukturelle Probleme liegen. Dies unterst\u00fctzt sowohl die Wartbarkeit als auch die Weiterentwicklung des Systems, da <strong>Architekturentscheidungen<\/strong> nachvollziehbar und \u00fcberpr\u00fcfbar bleiben. Insgesamt f\u00fchrt dieser Ansatz zu einer st\u00e4rker kontrollierten, konsistenten und langfristig stabilen Softwarearchitektur.<\/p>\n<p>Automatisch generiert aus Code mal mit <a href=\"https:\/\/www.archunit.org\/userguide\/html\/000_Index.html\" target=\"_blank\">Archunit<\/a>, erzeugt z.B. die obige UML Datei (puml mit Eclipse-Plugin f\u00fcr autom. Rendern) mit folgenden Vorteilen:<!--more--><\/p>\n<p>&#8211; echte Paketabh\u00e4ngigkeiten<br \/>\n&#8211; keine manuelle Pflege von UML<br \/>\n&#8211; immer aktuell mit jedem Build<\/p>\n<p>Gute Architekturqualit\u00e4t (wichtig)<\/p>\n<p>Dieses Setup erzwingt:<\/p>\n<p>&#8211; Dynamische Architekturvisualisierung<br \/>\n&#8211; keine veralteten Diagramme mehr<br \/>\n&#8211; &#8222;Living Architecture&#8220;<br \/>\n&#8211; CI-f\u00e4hig<\/p>\n<p>Hier der Java Code im Testpackage am Beispiel der Mammutb\u00e4ume:<\/p>\n<pre class=\"lang:java decode:true \" >package de.wenzlaff.taxonomie.arch;\r\n\r\nimport java.nio.file.Files;\r\nimport java.nio.file.Path;\r\nimport java.util.Set;\r\nimport java.util.stream.Collectors;\r\n\r\nimport org.junit.jupiter.api.Test;\r\n\r\nimport com.tngtech.archunit.core.domain.JavaClasses;\r\nimport com.tngtech.archunit.core.importer.ClassFileImporter;\r\nimport com.tngtech.archunit.junit.AnalyzeClasses;\r\nimport com.tngtech.archunit.junit.ArchTest;\r\nimport com.tngtech.archunit.lang.ArchRule;\r\n\r\nimport static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;\r\n\r\n\/**\r\n * Erzeugen einer target\/taxonomy-archirecture.puml Datei.\r\n * \r\n * @author Thomas Wenzlaff\r\n *\r\n *\/\r\n@AnalyzeClasses(packages = \"de.wenzlaff.taxonomie\")\r\npublic class ArchitecturePlantUmlExportTest {\r\n\r\n    private final JavaClasses classes = new ClassFileImporter().importPackages(\"de.wenzlaff.taxonomie\");\r\n\r\n    \/\/ =========================================================\r\n    \/\/ 1. Architekturregeln (optional zus\u00e4tzlich)\r\n    \/\/ =========================================================\r\n\r\n    @ArchTest\r\n    static final ArchRule model_is_isolated = noClasses().that().resideInAPackage(\"..model..\").should()\r\n\t    .dependOnClassesThat().resideInAPackage(\"..service..\");\r\n\r\n    \/\/ =========================================================\r\n    \/\/ 2. PlantUML Export Test\r\n    \/\/ =========================================================\r\n\r\n    @Test\r\n    void exportDependencyGraphAsPlantUML() throws Exception {\r\n\r\n\tStringBuilder uml = new StringBuilder();\r\n\tuml.append(\"@startuml\\n\");\r\n\tuml.append(\"title Taxonomie Architektur (ArchUnit Export)\\n\\n\");\r\n\r\n\t\/\/ =====================================================\r\n\t\/\/ Knoten (Pakete)\r\n\t\/\/ =====================================================\r\n\r\n\tuml.append(\"package model {}\\n\");\r\n\tuml.append(\"package service {}\\n\");\r\n\tuml.append(\"package data {}\\n\");\r\n\tuml.append(\"package arch {}\\n\\n\");\r\n\r\n\t\/\/ =====================================================\r\n\t\/\/ Abh\u00e4ngigkeiten extrahieren\r\n\t\/\/ =====================================================\r\n\r\n\tSet&lt;String&gt; edges = classes.stream().flatMap(c -&gt; c.getDirectDependenciesFromSelf().stream())\r\n\t\t.filter(dep -&gt; dep.getTargetClass().getPackageName().startsWith(\"de.wenzlaff.taxonomie\")).map(dep -&gt; {\r\n\t\t    String from = dep.getOriginClass().getPackageName();\r\n\t\t    String to = dep.getTargetClass().getPackageName();\r\n\t\t    return from + \" --&gt; \" + to;\r\n\t\t}).collect(Collectors.toSet());\r\n\r\n\t\/\/ =====================================================\r\n\t\/\/ Kanten schreiben\r\n\t\/\/ =====================================================\r\n\r\n\tfor (String edge : edges) {\r\n\r\n\t    String cleaned = edge.replace(\"de.wenzlaff.taxonomie.\", \"\").replace(\"..\", \"\");\r\n\r\n\t    uml.append(cleaned).append(\"\\n\");\r\n\t}\r\n\r\n\tuml.append(\"\\n@enduml\");\r\n\r\n\t\/\/ =====================================================\r\n\t\/\/ Datei schreiben\r\n\t\/\/ =====================================================\r\n\r\n\tPath output = Path.of(\"target\/taxonomy-architecture.puml\");\r\n\tFiles.createDirectories(output.getParent());\r\n\tFiles.writeString(output, uml.toString());\r\n    }\r\n}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Die Kombination aus ArchUnit und einem PlantUML-Export f\u00fcr 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\u00e4chlichen Codebestand abgeleitet wird. Das reduziert die Gefahr veralteter oder inkonsistenter Architekturdiagramme erheblich, da jede &hellip; <\/p>\n<p class=\"link-more\"><a href=\"http:\/\/blog.wenzlaff.de\/?p=23250\" class=\"more-link\"><span class=\"screen-reader-text\">\u201eAutomatisch UML Generierung (plantUML-Format) mit ArchUnit\u201c <\/span>weiterlesen<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_import_markdown_pro_load_document_selector":0,"_import_markdown_pro_submit_text_textarea":"","footnotes":""},"categories":[220,3161,3897,3163,79],"tags":[6324,4843,110,112,2178,6322,2182,6323,2180],"class_list":["post-23250","post","type-post","status-publish","format-standard","hentry","category-anleitung","category-build","category-java-programmierung","category-maven","category-programmierung","tag-arch","tag-archunit","tag-autom","tag-beispiel","tag-java","tag-mammutbaeume","tag-programmierung","tag-puml","tag-uml"],"_links":{"self":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts\/23250","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=23250"}],"version-history":[{"count":3,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts\/23250\/revisions"}],"predecessor-version":[{"id":23253,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts\/23250\/revisions\/23253"}],"wp:attachment":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=23250"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=23250"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=23250"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}