Suche
Erosionen vorbeugen

Architektur-Management für Java-Projekte: Prinzipien & Grundlagen

Alexander von Zitzewitz

Gutes Architektur-Management ist mithin entscheidend für den Erfolg mittlerer und großer Softwareprojekte. Wir geben Ihnen hier einen Überblick über die wichtigsten Aspekte des Architektur-Managements, welches insbesondere für alle Projektleiter, Softwarearchitekten und nicht zuletzt für alle, die Softwareprojekte in Auftrag geben, von Relevanz sind.

Hauptziel des Architektur-Managements ist es sicherzustellen, dass die tatsächliche Struktur der zu entwickelnden Software mit der festgelegten logischen Architektur übereinstimmt. Ein weiteres Ziel besteht darin, über bewährte Designregeln und Nutzung bestimmter Metriken die technische Qualität der Codebasis auf hohem Niveau zu halten.

Nicht umsonst liegt der Schwerpunkt jedoch auf der Überwachung der tatsächlichen Struktur, denn strukturelle Probleme gehören sehr häufig zu den wichtigsten Gründen für das Scheitern von Softwareprojekten. Man spricht in diesem Zusammenhang gerne vom „Degenerieren“ oder der „Erosion“ von Architektur beziehungsweise Struktur. Erosion der Struktur vor allem, wenn in der Software mehr und mehr unerwünschte und zyklische Abhängigkeiten entstehen. Ein Beispiel für eine unerwünschte Abhängigkeit ist der berühmt berüchtigte Direktzugriff von der Datenzugriffs- in die Benutzer­oberflächenschicht. Dieses Phänomen kann man fast in jedem nicht trivialen Projekt beobachten, an dem mehrere Entwickler beteiligt sind. Die Ursachen für dieses Phänomen sind mannigfaltig:

  • Das Wissen über die geplante logische Architektur und die Konzepte dahinter ist ungleichmäßig im Team verteilt.
  • Das Einfügen unerwünschter Abhängigkeiten durch den Entwickler kann vom Compiler nicht erkannt werden. Ungekehrt ist es bei größeren und komplexeren Systemen für viele Entwickler oft sehr schwierig, erwünschte von unerwünschten Abhängigkeiten zu unterscheiden.
  • Mit wachsender Systemgröße und Komplexität wird es immer schwieriger, Architekturverletzungen einfach zu erkennen.
  • Softwareprojekte leiden praktisch immer unter Zeitdruck. Wer hat nicht schon einmal den gefürchteten „Quick Hack“ eingebaut, um einen Termin zu halten, und hatte im Anschluss nicht die nötige Zeit für eine saubere Lösung.
  • Kopplungsgrad und Komplexität der Software wachsen zumeist unbemerkt über ein leicht zu beherrschendes Maß hinaus. Erst zu spät bemerkt man, dass Änderungen immer schwieriger werden und oft unerwünschte Effekte in eigentlich nicht zusammengehörenden Bereichen der Software hervorrufen.

Gerade der letzte Punkt verdeutlicht, warum man möglichst frühzeitig eine Strategie zur Bekämpfung der Erosion von Struktur erwägen sollte. Denn der Effekt, dass Änderungen immer schwieriger sind, ist ein untrüglicher Hinweis für schwerwiegende Probleme in der inneren Struktur der Software. Ein solches Symptom sollte daher von allen Projektverantwortlichen als ein ernst zu nehmendes Alarmsignal betrachtet werden.

Definition einer logischen Architektur

Was also kann man tun, um dieses Problem in den Griff zu bekommen? Als Erstes ist es notwendig, die logische Architektur des Systems formal zu beschreiben, denn ohne eine solche Beschreibung ist es überhaupt nicht möglich festzustellen, ob die geplante Struktur von der Codebasis auch eingehalten wird.

Wir schlagen eine Architektur vor, die durch ein horizontales und vertikales Schneiden des Systems in Schichten (Layers) und fachliche Schnitte (Vertical Slices) gekennzeichnet ist [Robert C. Martin: Agile Software Development, Prentice-Hall 2003]. Die Schichten werden nach technischen Kriterien geschnitten. Klassische Beispiele für Schichten sind Benutzeroberfläche, Geschäftslogik und Datenzugriff. Die fachlichen Schnitte werden erwartungsgemäß nach fachlichen Kriterien angesetzt. Sobald man die Schichten und fachlichen Schnitte festgelegt hat, definiert man die erlaubten Abhängigkeiten zwischen den Schichten bzw. fachlichen Schnitten. Dabei ist streng darauf zu achten, dass keine Zyklen entstehen. Auf die Gründe, warum Zyklen in jedem Fall vermieden werden sollten, werde ich später noch ausführlicher eingehen.

Die Abbildung 1 zeigt eine beispielhafte logische Architektur für ein fiktives System. Es gibt dort vier Schichten und drei fachliche Schnitte. Die View-Schicht darf sowohl die Controller- als auch die Domain-Schicht benutzen. Alle anderen Schichten haben nur Zugriff auf die jeweils darunter liegende Schicht. Für die fachlichen Schnitte gilt, dass „Verträge“ die beiden anderen Schnitte benutzen darf, „Kunden“ darf hingegen lediglich auf Benutzer zugreifen. Der Schnitt „Benutzer“ hat keine ausgehenden Abhängigkeiten zu anderen fachlichen Schnitten.

Abb. 1: Logische Architektur mit Schichten und vertikalen Schnitten

Man kann sehr gut erkennen, wie durch das Schneiden in zwei Dimen­sionen automatisch Blöcke in der Architektur entstehen, die wir als „natürliche Subsysteme“ bezeichnen möchten. Das Schöne an unserem Konzept besteht nun darin, dass sich für diese Subsysteme von ganz allein erlaubte und unerlaubte Abhängigkeiten ergeben, wenn wir auf die Regeln der darüber liegenden Schichten und fachlichen Schnitte zurückgreifen.

Für Subsytem „1“ ergibt sich automatisch, dass es auf keines der anderen Subsysteme in der Abbildung zugreifen kann, weil die Schicht „Datenzugriff“ und der Schnitt „Benutzer“ keine ausgehenden Abhängigkeiten haben. Subsystem „2“ hingegen hat Zugriff auf die Subsysteme „a“, „b“ und „1“. Die Beziehung zu „a“ ergibt sich aus der Schnittbeziehung zwischen „Kunden“ und „Benutzer“, die Beziehung zu „b“ aus der Schichtbeziehung zwischen „Domain“ und „Datenzugriff“. Die Beziehung zu „1“ resultiert dann aus der Kombination der beiden zuvor genutzten Beziehungen. Zusätzlich gibt es in praktisch jedem System rein technische Subsysteme, die nur zu einem Layer gehören, aber keinem fachlichen Schnitt zugeordnet werden können. Beispiele für solche technischen Subsysteme wären „log4j“, „reflection“ oder „Hibernate“.
Nun sind wir in der Lage, ein partielles Metamodell für unsere logische Architektur zu definieren: Ein Subsystem gehört immer zu einer Schicht und eine Schicht kann beliebig viele Subsysteme enthalten. Optional kann ein Subsystem auch zu einem fachlichen Schnitt gehören. Diese Zugehörigkeit ließe sich beispielsweise per Namenskonvention regeln. Wenn wir jedem Subsystem innerhalb einer Schicht einen eindeutigen Namen geben, können wir einfach festlegen, dass ein Subsystem genau dann auch zu einem fachlichen Schnitt gehört, wenn es denselben Namen wir der fachliche Schnitt hat. Da wir die Namen frei vergeben können, entstehen durch diese Konvention keine Einschränkungen.

Abbildung der logischen Architektur auf die Java-Ebene

Im nächsten Schritt ist es notwendig, unsere Codebasis auf die bisher noch frei schwebende logische Architektur abzubilden. Ein möglicher Ansatz besteht da­rin, Subsysteme als eine Menge von Java-Packages zu definieren. Um ein gegebenes Softwaresystem nun auf eine gegebene Architektur abzubilden, genügt es, jedes Package genau einem Subsystem zuzuordnen.

Bei der Wahl einer geeigneten Namenskonvention für Packages kann die Zuordnung bequem über Wildcard-Muster beschrieben werden. In unseren Projekten verwenden wir die folgende Namenskonvention:

com.hello2morrow.proj.vertical-slice.layer...

Am Ende kann irgendein Postfix stehen. Wir haben den fachlichen Schnitt im Namen bewusst vor den Layer gestellt. Damit liegen zusammengehörende fachliche Komponenten immer im selben Verzeichnisbaum. Da fachliche Schnitte am ehesten auch deploybare Einheiten sind, macht das unser Leben an vielen Stellen um einiges leichter. Natürlich sind auch andere Namenskonventionen denkbar, Hauptsache, es steckt ein nachvollziehbares und konsistentes System dahinter.

Schnittstellen für Subsysteme

Wenn wir nun die Subsysteme noch einmal genauer betrachten, bleibt ein kleines Problem übrig: die Java-Schnittstelle des Subsystems besteht aus allen öffentlichen Typen aller Packages des Subsystems. In der Regel möchte man aber den externen Zugriff auf ein Subsystem auf eine möglichst schmale Schnittstelle begrenzen. Aus diesem Grund fehlt in unserem Architekturmodell noch das Konzept sog. Named Interfaces.

Ein Named Interface ist nichts anderes als eine Teilmenge der öffentlichen Typen eines Subsystems. So könnte man z.B.  den externen Zugriff auf ein Subsystem aus mehreren Packages auf alle öffentlichen Typen eines „api“ genannten Package begrenzen. Außerdem ist es durchaus denkbar, für ein gegebenes Subsystem mehrere Named Interfaces zu definieren. In einem konkreten Kundenprojekt hatte fast jedes Subsystem ein schmales allgemein zugängliches Interface und ein etwas breiteres Interface für alle Subsysteme aus demselben fachlichen Schnitt.

Aus den bisherigen Überlegungen können wir nun ein vollständiges Metamodell für eine logische Architektur definieren. Abbildung 2 enthält eine Darstellung dieses Metamodells.

Abb. 2: Metamodell einer logischen Architektur
Verfeinerung einer logischen Architektur

Sobald wir die groben Linien der Architektur festgelegt haben, geht es um die Verfeinerung. Dazu gehört vor allem die Definition von Named Interfaces für einen Teil der Subsysteme und die Definition zusätzlicher Regeln, die von den verbleibenden legalen Subsystem Beziehungen einen Teil verbieten. So ist es beispielsweise in den meisten Projekten durchaus sinnvoll, die Nutzung von java.lang.reflection nur einigen bestimmten Subsystemen zu gestatten.

Es bleibt natürlich anzumerken, dass eine logische Architektur nicht irgendetwas Statisches ist, das einmal zu Beginn des Projekts festgelegt wird und sich dann nie mehr verändert. Im Gegenteil, die logische Architektur entsteht erfahrnungsgemäß inkrementell und wird im Projektverlauf erweitert und verfeinert.

Geschrieben von
Alexander von Zitzewitz
Kommentare
  1. Andrej Albrecht2016-10-31 21:50:11

    Hallo Alexander,

    vielen Dank erstmal für diesen tollen Artikel, den ich bereits öfters weiter empfohlen habe.

    Könntest du auf die Schnittstellen für Subsysteme etwas genauer eingehen und beschreiben wie deine Idee der Eingrenzung des Zugriffs mit Java und Named Interfaces umgesetzt werden kann?

    Beste Grüße
    Andrej

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.