Von Silas Graffy

Estimated reading time: 6 Minuten

Techblog

Architektur absichern in Java

Was ist eigentlich Softwarearchitektur? Gemäß ISO/IEC 42010 sind das die “fundamental concepts or properties of a system in its environment embodied in its elements, relationships, and in the principles of its design and evolution.” Eine ganze Menge also, mit der wir uns zu beschäftigen haben. Das tun wir in aller Regel, um Qualitätsziele (oft nach ISO/IEC…

Techblog

Was ist eigentlich Softwarearchitektur? Gemäß ISO/IEC 42010 sind das die

“fundamental concepts or properties of a system in its environment embodied in its elements, relationships, and in the principles of its design and evolution.”

Eine ganze Menge also, mit der wir uns zu beschäftigen haben. Das tun wir in aller Regel, um Qualitätsziele (oft nach ISO/IEC 25010) einhalten zu können. Dazu zählen u. a. Zuverlässigkeit, Performance, Nutzbarkeit, Portabilität und Wartbarkeit – wobei ich persönlich lieber von Änderbarkeit spreche, da immaterielle Güter wie Software schließlich nicht durch Nutzung verschleißen und folglich auch keine Wartung erfordern.

Zur Erreichung vieler dieser Ziele kann eine angemessene interne Struktur der Software beitragen, also elements und relationships in der o. g. Definition – oder umgangssprachlich nach gängigen Visualisierungsformen oft auch einfach Boxes and Arrows. In Simon Browns C4-Model zur Beschreibung von Softwarearchitektur entspricht dies den unteren beiden Ebenen (Containers und Classes) und auch das hervorragende freie Template für Architekturdokumentation arc42 widmet diesem Thema mit der Building Block View ein eigenes Kapitel.

Hat man sich nun also Gedanken darüber gemacht, aus welchen Bausteinen die eigene Software aufgebaut werden soll und wie diese voneinander abhängen, nutzt all das wenig, wenn wir Entwickler im mitunter stressigen Projektalltag versehentlich und unbemerkt gegen die selbst gewählten Regeln verstoßen. Die Folge sind Architekturerosionen, die mitunter im Nachhinhein nur noch mühsam behoben werden können. Allerdings lassen sie sich leicht von vornherein vermeiden.

ArchUnit

ArchUnit ist eine quelloffene Library, mit der Architekturconstraints leicht in Java-Code formuliert und automatisch mittels Unit-Tests überprüft werden können.

Man bindet es einfach als Test-Dependency ins eigene Projekt ein, zum Beispiel mittels Gradle: testCompile 'com.tngtech.archunit:archunit-junit:0.5.0'. Ich demonstriere die Nutzung anhand eines Beispiels, das die Einhaltung einer Ports-and-Adapters-Architektur sicherstellt. Bei diesem Stil, auch hexagonale oder Onion-Architektur genannt, wird die Anwendung in Schichten um das Domain Model im Kern aufgebaut:

Abhängigkeiten sind dabei immer nur von Außen nach Innen erlaubt und nie anders herum:

@Test
public class OnionArchitectureTests {
    private final JavaClasses classes = new ClassFileImporter().importPath("build/classes/main");
​
    @Test
    public void domain_model_has_no_dependencies_to_domain_service() {
        noClasses().that().resideInAPackage("..domain.model..").should().accessClassesThat().resideInAPackage("..domain.service..").check(classes);
    }
​
    @Test
    public void domain_has_no_outgoinng_dependencies() {
        noClasses().that().resideInAPackage("..domain..").should().accessClassesThat().resideInAPackage("..application..").check(classes);
        noClasses().that().resideInAPackage("..domain..").should().accessClassesThat().resideInAPackage("..infrastructure..").check(classes);
        noClasses().that().resideInAPackage("..domain..").should().accessClassesThat().resideInAPackage("..restapi..").check(classes);
        noClasses().that().resideInAPackage("..domain..").should().accessClassesThat().resideInAPackage("..serialization..").check(classes);
    }
​
    @Test
    public void application_has_only_dependencies_to_domain() {
        noClasses().that().resideInAPackage("..application..").should().accessClassesThat().resideInAPackage("..infrastructure..").check(classes);
        noClasses().that().resideInAPackage("..application..").should().accessClassesThat().resideInAPackage("..restapi..").check(classes);
        noClasses().that().resideInAPackage("..application..").should().accessClassesThat().resideInAPackage("..serialization..").check(classes);
    }
​
    @Test
    public void infrastructure_does_not_depend_on_rest_api() {
        noClasses().that().resideInAPackage("..infrastructure..").should().accessClassesThat().resideInAPackage("..restapi..").check(classes);
    }
​
}

Das Beispiel ist so eins zu eins aus einem aktuellen Kundenprojekt übernommen und lässt sich auf alle Projekte übertragen, in welchen die Ringe der Architektur in den Package-Namen kodiert sind. Dank der ..-Platzhalter müssten diese nicht einmal angepasst (oder für die Veröffentlichung anonymisiert) werden. Über einen ClassFileImporter wird nur der Production-Code im Test überprüft.

Bekannte Architekturverletzungen (Technical Debt) können mittels einer Datei mit dem Namen archunit_ignore_patterns.txt im Classpath Root-Verzeichnis ausgeschlossen werden. Auf diese Weise kann ArchUnit auch in bestehende Projekte integriert werden und zumindest den Neuverstoß gegen Archikturvorgaben verhindern.

Weitere Features, beispielsweise das Auffinden zyklischer Abhängigkeiten, Formulierungsalternativen für Schichtenarchitekturen sowie die Prüfung von Coding Rules, beschreibt der User Guide. Exceptions, Logging und Annotationen sind nur einige Beispiele. Auch eigene Regeln lassen sich implementieren.

Alternativen

Das mit Java 9 eingeführte Modulsystem Project Jigsaw ist bereits eine große Hilfe bei der Vermeidung versehentlich eingeführter Abhängigkeiten, da man Modulabhängigkeiten hier explizit angibt. Leider hilft das im Kontext bestehender Anwendungen nicht viel weiter, anders als ArchUnit inklusive seiner archunit_ignore_patterns.txt-Datei .

Ebenfalls quelloffen und sehr mächtig ist jQAssistant. Dessen Dokumentation beschreibt mit dem Einsatzzweck “Definition und Validierung projektspezifischer Regeln auf struktureller Ebene” und der technischen Unmsetzung auf Basis der Graph-Datenbank Neo4j auch gleich die grundlegenden Gemeinsamkeiten und Unterschiede mit und zu ArchUnit. Daraus folgen verschiedene Vor- und Nachteile. Nachteilig sind sicher die kompliziertere Einrichtung (insbesondere bei bestehenden Projekten) und die steilere Lernkurve. Überaus positiv ist dagegen die Vielseitigkeit zu bewerten. Unter anderem existieren zahlreiche Plugins, die jQA erweitern. Ein schönes Beispiel gibt die Demo Uneven Modules, in der jQA genutzt wird, um in Asciidoc formulierte Architekturregeln auf Einhaltung zu überprüfen (inkl. Zykluserkennung und unter Ausschluss von ebenfalls in Asciidoc formulierten Tech Debt-Ausnahmen), (Plant)UML-Diagramme zu zeichnen und all das zu einer arc42-Architekturdokumentation in Asciidoc zusammenzuführen (hier geht es zu einem erläuternden Blog-Post). Flexible Abfragen und schöne Visualisierungen der Ergebnisse liefert die Neo4j Console dazu frei Haus.

Nicht nur in unseren System- und Architekturaudits setzen wir Structure 101 Studio sehr gerne ein. Anders als die bisher vorgestellten Werkzeuge handelt es sich dabei um ein kommerzielles Produkt, das leider nicht ganz günstig ist. Dafür bietet es außer der Definition und Validierung von Architekturregeln die Möglichkeit, eine bestehende Ist-Architektur schnell zu visualisieren und interaktiv zu erkunden. Dabei werden Abhängigkeitszyklen automatisch hervorgehoben. Im Gegensatz zu ArchUnit kann Structure 101 auch unerwünschte Abhängigkeiten in Interfaces und reinen Deklarationen erkennen. Neben Java gibt es auch Versionen für .NET und weitere Plattformen.

Ähnliches gilt für die Sonargraph Produktfamilie. Hier kann man jedoch eine kostenlose Lizenz für die nicht-kommerzielle Nutzung beantragen. Leider konnte ich mit Sonargraph noch keine eigenen Erfahrungen sammeln – umso mehr würde ich mich über entsprechende Kommentare freuen!

Fazit

Softwarearchitektur ist kein Selbstzweck, sondern ist eine wichtige Voraussetzung für die Verständlichkeit der Codebasis und damit für Änderbarkeit und die Einhaltung anderer Qualitätsziele. Leider hält Architekturerosion im mitunter stressigen Projektalltag schneller Einzug als wir alle wünschen. ArchUnit et al. können idealerweise vollautomatisch im Build-Prozess die Regelkonformität der Architektur sicherstellen – selbstverständlich neben Maßnahmen wie Code Reviews, Pair Programming, Collaborative Ownership für Architektur und Code.


Von Silas Graffy

IT-Sanierung

Silas stieß 2015, nach 15 Jahren Produktentwicklung & Forschung, zu MaibornWolff. Schwerpunkte des Informatikers aus Leidenschaft bilden Software-Architekturen, agile Softwareentwicklung und System- & Architektur-Audits. Software- und Code-Qualität sind ihm ebenso ein Herzensanliegen wie Teamkultur und Collective Ownership. Er hat Abschlüssse in angewandter und Wirtschaftsinformatik. In der Freizeit gelten für ihn die wichtigen 4C: Code, Coffee, Cocktails — and Climbing.