Softwareentwicklung und Architektur / 22.04.2021 / Holger Tiemeyer

Flexible Softwarearchitektur mit Java

Flexibilität und Modularität sind zwei wichtige Merkmale moderner Softwarearchitekturen. Anstelle eines großen, in sich geschlossenen Monolithen bestehen sie aus dezentralen Einzelbausteinen. Diese lassen sich nach Bedarf austauschen oder auch neu kombinieren, um die Anwendungen und Funktionalitäten des Systems dynamisch zu verwalten. Die vorliegende Artikelserie beschreibt, wie sich Flexibilität in Java-Softwarearchitekturen durch Modularisierung erreichen lässt. Der erste Teil zeigt auf, welche Prinzipien hinter einer solchen flexiblen Architektur stehen sowie mit welchen Vorteilen und Herausforderungen sie verbunden ist.

 

Die Grundprinzipien der Modularisierung ermöglichen Flexibilität auf den drei gängigen Architekturebenen

Die Modularität eines Systems trägt viel zu seiner Anwendbarkeit bei. Als Modul wird in der Softwarearchitektur eine funktional geschlossene Einheit bezeichnet, die einen Dienst bereitstellt, ohne dabei von anderen Einheiten abhängig zu sein. Die Modularität eines Systems erlaubt es, einzelne Komponenten auszutauschen, ohne dabei das gesamte System verändern zu müssen.

Insofern legt sie den Grundstein für eine praktische Softwarearchitektur. Bereits 1972 wurden in einer Veröffentlichung von D.L. Parnas Kriterien für die Zerlegung eines Systems in Module vorgeschlagen:

„The effectiveness of a ‘modularization‘ is dependent upon the criteria used in dividing the system into modules.“

Parnas schlägt vor, mit einer Liste von Entwurfsentscheidungen zu beginnen, die schwierigen, dynamischen und vielen Änderungen unterliegen. Jedes Modul, das entworfen wird, sollte eine dieser Entwurfsentscheidung jeweils vor den anderen verstecken („kapseln“). Hierdurch würden zwei Prinzipien erfüllt werden:

  1. Ein Modul sollte so wenig wie möglich über ein anderes Modul wissen und

  2. ein Modul sollte neu kompiliert und ersetzt werden können, ohne dass das gesamte System neu kompiliert werden muss.

Diese Grundprinzipien finden sich heutzutage auf jeder der drei gängigen Architekturebenen in unterschiedlichen Ausprägungen wieder – nämlich der Domänen-, der Makro- und der Mikroarchitektur. Die Dekompositionskriterien für Module lassen sich hierbei anhand der ISO-Norm-25010 festlegen. Die durch die Norm definierten Qualitätsmerkmale für Software-Systeme erlauben die Ableitung von Qualitätszielen und somit die Bestimmung von Modulgrenzen.

 

Modularität erhöht die Flexibilität des Systems

Damit tatsächlich eine Modularität vorliegt, müssen die Module entkoppelt sein. Denn eine zu starke Kopplung von Klassen und Methoden führt häufig zu einem „Big Ball of Mud“. Damit bezeichnen Softwarearchitekten eine Applikation, die über keine erkennbar strukturierte Architektur verfügt. Der Big Ball of Mud lässt sich nur schwer warten und nicht systematisch weiterentwickeln.  

Insbesondere erhöht eine Entkopplung die Flexibilität des (Software-)Systems. Zur Entkopplung von Modulen gibt es zwei etablierte Strukturierungsmöglichkeiten:

  1. Interfaces: Durch die Verwendung von Interfaces lässt sich durch vertraglich zugesicherte Vorgaben sowohl in Richtung der Implementierung als auch in Richtung der Konsumenten des Moduls eine Entkopplung herstellen.

  2. Entwurfsmuster: Entwurfsmuster sind Lösungsschablonen für wiederkehrende Probleme, die sich in Erzeugungsmuster, Strukturmuster und Verhaltensmuster untergliedern und zu einer Entkopplung von Modulen und Komponenten beitragen.

Die beiden Prinzipien kommen kombiniert zum Einsatz, um eine direkte Abhängigkeitsbeziehung einzelner Module untereinander aufzulösen.

 

Modulgrenzen und Kapselung im Java-Modulsystem

Eine weitere Flexibilisierung kann durch die Definition von Services durch das Projekt „Jigsaw“ erreicht werden. Jigsaw wurde mit Java 9 eingeführt, ermöglicht die Kapselung und eine erweiterte Festlegung der Außensicht auf ein Modul sowie die Definition von Services. Das Hinzufügen einer module-info.java-Datei in dem Wurzelverzeichnis einer Package-Hierarchie definiert die modularen Aspekte für ein Modul in Java.

Neben diesen beiden Strukturierungsmöglichkeiten existiert in Java zudem die Möglichkeit der weiteren Entkopplung und Erweiterung von Modulen über Services. Ein Service ist eine autarke Einheit, die über eine klar definierte Schnittstelle Funktionalitäten nach außen anbietet (engl. provides).

Diese können von Service-Konsumenten benutzt (engl. uses) werden. Mit der Einführung des Modul-Systems in Java wurde der seit der Version 6 im JDK vorhandene ServiceLoader überarbeitet. Starre Modulabhängigkeiten, die mittels der exports- und requires-Anweisungen entstehen, lassen sich in dem neu eingeführten Konzept durch angebotene und konsumierende Services auflösen. Hierdurch sind die Module weitestgehend entkoppelt. Ein neues Modul lässt sich nun einfach hinzufügen, ohne andere Module neu zu kompilieren.

Eine erweiterte Möglichkeit, Module auch zur Laufzeit aufzufinden und für die Ausführung zur Verfügung zu stellen, ist die Modularisierung mit OSGi. Wie diese genau funktioniert, wird im nächsten Artikel vorgestellt.

Eine ausführliche Version dieses Artikels mit praxisnahen Implementierungen und Codebeispielen wird voraussichtlich in Ausgabe 03-2021 der Fachzeitschrift Java aktuell erscheinen.

Einblicke

Shaping the future with our clients