cover-image

Image

Ulf Fildebrandt studierte Informatik an der Universität Hildesheim. Seit dem Diplomabschluss 1998 arbeitet er bei SAP in verschiedenen Technologiebereichen. Angefangen hat er mit UI-Technologien wie ActiveX und HTML, bis er über Java und Eclipse zu Integrationsarchitekturen im SOA-Bereich kam.

Seit einigen Jahren ist er als Architekt für verschiedene Produkte von SAP wie das SAP Netweaver Composition Environment verantwortlich.

Software modular bauen

Architektur von langlebigen Softwaresystemen -
Grundlagen und Anwendung mit OSGi und Java

Ulf Fildebrandt

Image

Ulf Fildebrandt
E-Mail: ulffildebrandt@aol.com

Lektorat: Dr. Michael Barabas
Copy Editing: Ursula Zimpfer, Herrenberg
Herstellung: Nadine Thiele
Umschlaggestaltung: Helmut Kraus, www.exclam.de
Druck und Bindung: M.P. Media-Print Informationstechnologie GmbH, 33100 Paderborn

Bibliografische Information der Deutschen Nationalbibliothek
Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie;
detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.

ISBN:
Buch 978-3-86490-019-8
PDF 978-3-86491-182-8
ePub 978-3-86491-183-5

1. Auflage 2012
Copyright © 2012 dpunkt.verlag GmbH
Ringstraße 19B
69115 Heidelberg

Die vorliegende Publikation ist urheberrechtlich geschützt. Alle Rechte vorbehalten. Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder die Verwendung in elektronischen Systemen.
Es wird darauf hingewiesen, dass die im Buch verwendeten Soft- und Hardware-Bezeichnungen sowie Markennamen und Produktbezeichnungen der jeweiligen Firmen im Allgemeinen warenzeichen-, marken- oder patentrechtlichem Schutz unterliegen.
Alle Angaben und Programme in diesem Buch wurden mit größter Sorgfalt kontrolliert. Weder Autor noch Verlag können jedoch für Schäden haftbar gemacht werden, die in Zusammenhang mit der Verwendung dieses Buches stehen.
5 4 3 2 1 0

Für meinen Vater

Inhaltsverzeichnis

1        Einleitung

1.1     Über dieses Buch

1.2     Motivation

1.3     Nachstellen der Beispiele

1.4     Regeln

2        Grundlagen

2.1     Definitionen

2.2     Grundthese

2.3     Prinzipien und Konzepte

2.4     Modularity Patterns

2.5     Vorgehen zur Definition modularer Architektur

2.6     Modularitätsmodell (Modularity Maturity Model)

3        Coding-Architektur: Erweiterbarkeit

3.1     Hintergrund von Coding Best Practices

3.2     Beschreibung des Anwendungsbeispiels

3.3     Factory Pattern

3.4     Decision Map

3.5     Single Responsibility

3.6     Nachstellen des Beispiels

3.7     Exkurs: Adapter und Facade

3.8     Zusammenfassung

4        Komponentenarchitektur: Grundlagen

4.1     Hintergrund eines modularen Laufzeitsystems

4.2     Umsetzung der Grundkonzepte am Beispiel OSGi

4.3     Auswirkungen eines modularen Laufzeitsystems auf Patterns

4.4     Umsetzung von Modularität in verschiedenen Laufzeitumgebungen

4.5     Modularisierung durch Refactoring

4.6     Zusammenfassung

5        Komponentenarchitektur: Entkopplung

5.1     Hintergrund modularisierter Software

5.2     Grundlagen von Declarative Services

5.3     Entkopplung in der Beispielanwendung

5.4     Exkurs: Zwei weitere SOLID-Prinzipien

5.5     Hierarchien in der Beispielanwendung

5.6     Schnittstellen in Modulen

5.7     Zusammenfassung

6        Systemarchitektur: Schichten

6.1     Hintergrund von Schichten in der Architektur

6.2     Entkoppelte Komponenten in der Beispielanwendung

6.3     Gruppierung von Bundles

6.4     Schichtenarchitektur mit OSGi

6.5     Exkurs: Testbarkeit

6.6     Zusammenfassung

7        Systemarchitektur: Erweiterbarkeit

7.1     Hintergrund von Schnittstellen

7.2     Schnittstellen in der Beispielanwendung

7.3     Erweiterung der Schnittstellen

7.4     Erweiterbarkeit mit OSGi

7.5     Ersetzbarkeit von Implementierungen

7.6     Kohäsion in der Beispielanwendung

7.7     Zusammenfassung

8        Systemarchitektur: Wiederverwendbarkeit entkoppelter Komponenten

8.1     Hintergrund entkoppelter wiederverwendbarer Komponenten

8.2     Entkopplung von externen Abhängigkeiten

8.3     Gemeinsam genutzte Aspekte

8.4     Zusammenfassung

9        Systemarchitektur: Funktionale Entkopplung

9.1     Hintergrund funktionaler Entkopplung

9.2     Parallele Verarbeitung in der Beispielanwendung

9.3     Versionierung in der Beispielanwendung

9.4     Zusammmenfassung

10      Komponentenarchitektur: Frameworks

10.1   Hintergrund der Frameworks

10.2   Umsetzung der Beispielanwendung mit CDI

10.3   Umsetzung der Beispielanwendung mit Spring

10.4   Umsetzung der Beispielanwendung mit Maven

10.5   Nachstellen des Beispiels

10.6   Exkurs: Verwendung von OSGi

10.7   Zusammenfassung

11      Systemarchitektur: Product Line Engineering

11.1   Hintergrund zu Product Line Engineering

11.2   Anwendung von PLE auf die Beispielanwendung

11.3   Mehrere Produkte

11.4   Nachstellen des Beispiels

11.5   Zusammenfassung

12      Systemarchitektur: Repository

12.1   Hintergrund von Repositories

12.2   Ein Repository für OSGi

12.3   Das p2-Repository für die Beispielanwendung

12.4   Verschiedene Einsatzmöglichkeiten

12.5   Maven-Repository

12.6   Zusammenfassung

13      Schlusswort

13.1   Zusammenfassung

13.2   Ausblick

A       Appendix A: Tools zur Architekturvalidierung

A.1    Korrektheit

A.2    Metriken

A.3    Redundanz

B       Appendix B: Projekt Jigsaw

B.1    Designprinzipien

B.2    Definitionen

B.3    Moduldeklaration

B.4    Vergleich mit OSGi

C       Appendix C: OSGi in der Praxis

C.1    Lifecycle von Bundles

C.2    Statusinformationen eines OSGi-Laufzeitsystems

Referenzen

Index

1 Einleitung

»In every phenomenon the beginning remains always the most notable moment.«

Thomas Carlyle

Modularität eines Systems ist ein wichtiges Thema in der Softwareentwicklung, und für alle wichtigen Themen gibt es verschiedene Ansätze, wie man sich ihm nähern kann. Manchmal steht die allgemeine Struktur von Software im Vordergrund mit einer Anzahl von abstrakten Patterns und Verhaltensweisen zur Entwicklung. Dann wiederum spielen die technischen Frameworks und Programmiersprachen die Hauptrolle inklusive der letzten Tricks und Tipps bei der Verwendung dieses oder jenes Frameworks.

Um Modularisierung zu verinnerlichen, bietet sich eine Mischung der beiden Ansätze von Theorie und Praxis an. In diesem Buch geht es darum, Konzepte anhand von konkreten Beispielen mithilfe von Frameworks zu erklären. Die Prinzipien eines modularen Systems werden beschrieben, um Flexibilität und Erweiterbarkeit zu erreichen. In der täglichen Arbeit ist es schwierig, die Patterns im Coding umzusetzen. Sie entsprechen niemals dem aktuellen Problem, und meistens ist das direkte Implementieren einfacher als die Verwendung von Patterns.

Um die Hürde bei der Anwendung von Prinzipien auf eine konkrete Implementierung zu überwinden, wurde dieses Buch geschrieben. Es soll eine Brücke schlagen zwischen den abstrakten Patterns und Konzepten und der realen Implementierung.

Erfahrene Softwareentwickler werden sicher noch weitere Patterns finden, die in diesem Umfeld sinnvoll sind. Vollständigkeit aller Patterns ist nicht unbedingt angestrebt. Ein Entwickler soll in die Lage versetzt werden, ein modulares System zu definieren und zu implementieren. Das System sollte flexibel an spätere Anforderungen angepasst werden können. Die Auswahl der Konzepte ist deshalb rein subjektiv und basiert auf jahrelanger Projekterfahrung bei der Erstellung von erweiterbaren Systemen.

Es ist nicht das Ziel, die letzten Details eines Frameworks zu beschreiben. Bei den Beispielen lässt es sich aber nicht vermeiden, dass die Frameworks kurz eingeführt werden, um die Konzepte an Coding-Beispielen in den aktuellen Frameworks für erweiterbare und flexible Systeme zu erläutern. Auf keinen Fall soll dadurchein Framework besser dargestellt werden als ein anderes. Manche Frameworks besitzen zwar Vorteile bei der Strukturierung des Codings, aber das soll die Beschreibung des Konzeptes vereinfachen, und nicht ein Framework als das beste beschreiben.

OSGi (Open Services Gateway initiative), eine Spezifikation für ein modulares Laufzeitsystem, spielt eine große Rolle in diesem Buch, weil es sich sehr gut eignet, um Modularität zu verdeutlichen, aber es ist keineswegs das einzige Framework, das man verwenden kann. OSGi ist eine Technologie, aber Technologien sind keine Architektur. Ein Abschnitt beschäftigt sich daher mit einem Vergleich möglicher alternativer Frameworks und wie man sie für den Anwendungsfall verwenden kann.

1.1 Über dieses Buch

1.1.1 Zielgruppe

Das Buch richtet sich an Entwickler, die daran interessiert sind, die Modularität eines Softwaresystems zu verbessern, indem Konzepte der Modularisierung und Komponentisierung angewendet werden. Ziel ist, ein möglichst flexibles System zu implementieren, offen für Erweiterungen. Da die Beispiele in Java implementiert sind, sollten die Leser die Grundlagen dieser Programmiersprache verstanden haben.

Das Buch richtet sich nicht an Leser, die eine allgemeine Beschreibung von Patterns suchen. Dafür gibt es eine Reihe von Büchern, die alle Konzepte im Detail erklären. Es ist auch nicht bestimmt für Entwickler, die ein Lehrbuch über die Programmierung in Java suchen. Dazu wird zu wenig Fokus auf die Programmierung gelegt.

Architekten können dieses Buch als Checkliste für die Implementierung eines Systems verwenden. Sollten einige Regeln nicht angewendet werden, dann muss das nicht negativ für das System sein, aber zumindest sollte der Architekt über das zugrunde liegende Problem nachgedacht haben. Danach kann er eine bewusste Entscheidung treffen.

Die Beispiele dienen dazu, die Lücke zwischen den abstrakten Patterns und dem täglichen Coding zu schließen. Im Allgemeinen handelt es sich um eine Transferleistung, aber diese Leistung ist genau das Entscheidende bei der Softwareentwicklung. Reale Projekterfahrung zeigt, dass es nur allzu oft passiert, dass in einem Projekt die Prinzipien und Patterns zwar bekannt sind, aber nicht angewendet werden. Dabei ist die Übersetzung der abstrakten Patterns in der realen Implementierung ein sehr wichtiger Qualitätsfaktor bei der Softwareentwicklung.

1.1.2 Aufbau

Das Buch ist in fünf Abschnitte aufgeteilt. Der erste Abschnitt mit dem Kapitel 2 führt die grundlegenden Prinzipien ein und gibt den roten Faden vor. Alle anderen Abschnitte konzentrieren sich auf die Konzepte, Prinzipien und Patterns. Dabei steigt der Abstraktionsgrad der angewendeten Konzepte, angefangen beim Coding bis hin zum allgemeinen Design des Systems.

Jedes darauffolgende Kapitel beginnt mit einem kurzen Abschnitt über die Theorie des jeweiligen Themas. Dabei steht kein Framework im Vordergrund. Die Beschreibungen in den Unterkapiteln, speziell die Beispiele, verwenden Java und OSGi.

Die Kapitel 3-5 befassen sich mit Coding Patterns und wie man sie in einer konkreten Beispielanwendung umsetzen kann. Bereits in diesen Kapiteln wird eine einfache Beispielanwendung erstellt, die dann nach und nach durch die Anwendung von Konzepten und Prinzipien immer weiter verbessert wird. Besonderes Augenmerk liegt dabei auf der Modularität und Flexibilität der Anwendung und nicht auf Aspekten wie Performance oder Benutzbarkeit.

Die Ergebnisse von Architekturtools verdeutlichen die Konzepte. In verschiedenen Schritten des Refactorings werden dabei Änderungen im Aufbau des Systems herausgearbeitet.

Der nächste Abschnitt mit den Kapiteln 6-9 hat das Ziel, sich mit dem System als solchem zu beschäftigen: einerseits im Hinblick auf die verschiedenen hierarchischen Schichten des Systems, andererseits bezüglich der Einbindung des Systems in eine Landschaft mit anderen Systemen. Erweiterbarkeit spielt bei den Schichten der Architektur eine nicht zu vernachlässigende Rolle.

Im folgenden Abschnitt in Kapitel 10 geht es darum, Frameworks wie Spring, Guice oder CDI (Context and Dependency Injection, Teil der Java EE 6 Spezifikation) zu beschreiben, die sich dazu eignen, Modularität zu realisieren. Im Buch wird hauptsächlich OSGi als Basis eingesetzt, aber da es sich um allgemeine Konzepte handelt, lassen sie sich auch mit anderen Frameworks umsetzen. Wie die Konzepte in den anderen Frameworks angewandt werden, ist Gegenstand dieses Abschnitts.

Der abschließende Abschnitt mit den Kapiteln 11 und 12 widmet sich der Evolution eines Softwaresystems. Meistens wird Software nicht nur einmal geschrieben, sondern über die Jahre an neue Anforderungen angepasst und aus den bestehenden Komponenten werden neue Anwendungen zusammengestellt. In diesem Zusammenhang werden die Prinzipien des Product Line Engineering (PLE) erklärt.

1.1.3 Herangehensweise

Softwareentwicklung ist manchmal eine sehr theoretische Aufgabe, aber dieses Buch soll gerade die Theorie ein wenig näher an die tägliche Programmierrealität bringen. Daher werden die Vorgehensweisen und Beispiele aus Sicht eines Entwicklers namens Harald Schmidt geschildert. Er durchleidet alle Schwierigkeiten, die im normalen Entwicklerleben bei der Umsetzung eines modularen Softwareprojektes auftauchen können.

Harald Schmidt wird auf die Reise geschickt, die Implementierung eines bestehenden Systems zu verbessern. Welche Prinzipien und Konzepte er auf seinem Weg anwendet, werden die Leser des Buches erleben. Entscheidende Erkenntnisse werden in Form von Merksätzen im Text markiert.

1.2 Motivation

Es ist bereits in den vorherigen Abschnitten angeklungen: Die Prinzipien für ein modulares System werden mit dem Schwerpunkt auf die nicht-funktionalen Aspekte von Entkopplung, Erweiterbarkeit und Einfachheit erklärt.

Aber zunächst stellt sich die Frage, warum ein System überhaupt modular aufgebaut sein sollte. Wenn es nur darum geht, dass ein Stück Software eine Funktionalität bereitstellt, dann bietet es sich an, diese Funktionalität einfach zu implementieren. Die Software kann in Betrieb genommen werden.

Leider sind die Anwender in den wenigsten Fälle zufrieden und stellen weitere Anforderungen an das System. Das tun sie sowohl unmittelbar nach der Produktivschaltung als auch einige Jahre nach dem Start, wobei sich die Wünsche in beiden Fällen deutlich voneinander unterscheiden. In der ersten Zeit handelt es sich meist um Fragen nach Abrundungen und fehlenden Features, in der weiteren Lebenszeit des Systems kommen neue Geschäftsanforderungen hinzu und neue Technologien, die unterstützt werden müssen.

Das bedeutet, dass ein System während seiner gesamten Lebenszeit eine Evolution durchlaufen muss. Weiterentwicklung eines Monolithen ist nur sehr schwer möglich, denn neue Features beeinflussen das ganze System, sodass die Entwickler meist alle Teile im Blick haben müssen.

Wenn das System modular aufgebaut ist, dann ist eine Weiterentwicklung ohne Gefährdung und Beeinflussung des Gesamtsystems möglich. Hierbei sind im Prinzip zwei Veränderungen zu unterscheiden:

♦ Erweiterung bestehender Funktionen: Hier müssen an den existierenden technischen Komponenten Erweiterungspunkte vorgesehen sein, sodass die Erweiterung eingehängt werden kann. Neue Module wie in Abbildung 1-1 lassen sich hinzufügen und relativ einfach in Betrieb nehmen.

Image

Abb. 1-1 Erweiterbarkeit in einem modularen System

♦ Ersetzung bestehender Funktionen: Manche Teile der Software sind nicht optimal entwickelt, sei es, weil sie die Anforderungen, wie Performance, nicht erfüllen oder weil verwendete Komponenten einfach in neueren Versionen vorliegen. An dieser Stelle hilft die Entkopplung der Software weiter, denn Teile wie in Abbildung 1-2 lassen sich austauschen, ohne das gesamte System zu beeinflussen.

Image

Abb. 1-2 Entkopplung in einem modularen System

Wenn diese beiden Weiterentwicklungen in einem modularen System möglich sind, kann sich das System an neue Anforderungen anpassen. Wie diese Ziele in der Implementierung umgesetzt werden, soll in diesem Buch beschrieben werden.

1.3 Nachstellen der Beispiele

An dieser Stelle sind ein paar Worte zum Nachstellen der Beispiele nötig. Alle größeren Beispiele in diesem Buch sind auch auf GitHub verfügbar:

https://github.com/ModularityExamples

Bei Git handelt es sich um das im Moment gängigste Versionsverwaltungssystem für Sourcecode. Ein weiterer Vorteil ist, dass Open-Source-Projekte dort kostenlos zur Verfügung gestellt werden können. Es eignet sich daher sehr gut, um Coding für eine größere Anzahl von Entwicklern zugreifbar zu machen.

Außerdem ist die Integration in die Entwicklungsumgebung sehr gut und die Beispiele lassen sich ohne größere Probleme direkt nachstellen. Der grundsätzliche Aufbau und die Voraussetzungen aller Projekte sind gleich, sodass es sich anbietet, an dieser frühen Stellen darauf zu verweisen, wie die Projekte aufgesetzt werden können.

Als Beispiel soll die allererste Version der Beispielanwendung dienen. Es ist einfach eine beliebige Webanwendung ohne Anwendung irgendwelcher Prinzipien.

1.3.1 Voraussetzungen

Bevor ein Leser allerdings anfangen kann, die Beispiele auszuführen, sind die grundlegenden Setup-Fragen zu klären. Zur Entwicklung der Beispiele wurde die Eclipse-Version 3.7 verwendet. Man kann sich die zum Rechner passende Version von der Eclipse-Homepage herunterladen:

http://www.eclipse.org/downloads/.

Dort sind verschiedene Varianten vorhanden. Es ist zu empfehlen, mit Eclipse Classic anzufangen. Darin sind alle Tools zur Entwicklung von Java und OSGi Bundles enthalten.

Um jetzt die Quellen von GitHub herunterzuladen, ist noch ein Git-Client in Eclipse notwendig. Dazu gibt es ein eigenes Projekt, EGit, und die Eclipse-Installation kann erweitert werden. Unter Help Install New Software können UpdateSites eingetragen werden. Das EGit-Projekt ist unter

http://download.eclipse.org/egit/updates-1.3

verfügbar. Im Wizard müssen die Lizenzfragen beantwortet werden, danach kann man seine Eclipse-Installation mit GitHub verbinden. Dazu muss man in den Eclipse Preferences unter Team Image Git Image Configuration ein paar Einstellungen vornehmen:

user.name: stammt direkt aus der GitHub-Anmeldung

user.email: stammt direkt aus der GitHub-Anmeldung

Sehr gute Erklärungen, welche Schritte notwendig sind, um Eclipse mit GitHub zu verbinden, finden sich unter folgenden URLs:

♦ GitHub-spezifische Schritte: http://help.github.com/mac-set-up-git/

♦ EGit-Konfigurationen: http://www.vogella.com/articles/EGit/article.html

1.3.2 Synchronisieren von Projekten

Nachdem die Entwicklungsumgebung vorbereitet ist, kann man ein Repository aus GitHub in den Arbeitsbereich von Eclipse herunterladen, in der Git-Terminologie heißt das klonen (engl. clone). Dazu wechselt man in Eclipse in die »Git Repository Exploring«-Perspektive und wählt »clone repository« aus.

In den darauf folgenden Wizard trägt man als Nächstes die URL des Git-Repositories für die erste Version der Beispielanwendung ein:

git@github.com:ModularityExamples/Session0.git

Abbildung 1-3 zeigt die Perspektive und den Wizard.

Image

Abb. 1-3 Clone Repository in Eclipse EGit

Danach sollten alle Projekte importiert werden, denn bisher sind sie noch nicht im Arbeitsvorrat von Eclipse enthalten. In Abbildung 1-4 sieht man die dazu notwendige Aktion.

Nach dem Importieren müssen die Eigenschaften jedes Projektes angepasst werden. Dazu öffnet man auf dem Kontextmenü Properties Image Java Build Path. Im Tab für die Reihenfolge der Projektabhängigkeiten (engl. Order) selektiert man das JRE (Java Runtime Environment) in der passenden Version zur JavaLaufzeit und die Plug-in-Dependencies.

Anschließend muss man auf dem Kontextmenüs des Projektes noch PDE Tools Image Update Classpath ausführen und das Projekt sollte baubar sein.

Image

Abb. 1-4 Importieren der Projekte

1.3.3 Ausführen der Projekte

In allen Beispielen wird ein OSGi-Laufzeitsystem verwendet. Der Einfachheit halber wird für die Beispiele die Eclipse-Laufzeit mit verwendet.

In den GitHub-Repositories befinden sich dazu jeweils Startkonfigurationen (engl. launch configurations) für Eclipse (.launch-Dateien). Diese Dateien kann man über File Image Import Image Run/Debug Image Launch Configurations importieren und danach erscheinen sie als ausführbare Einheiten im Run-Menü von Eclipse.

Die Startkonfigurationen enthalten bereits alle notwendigen Bundles, um die Beispiele laufen zu lassen.

1.4 Regeln

In den folgenden Kapiteln werden viele Regeln beschrieben. Aber die Angabe von Regeln ist immer ein zweischneidiges Schwert. Im ersten Moment sind sie eine Hilfe, wenn man aber genauer darüber nachdenkt, dann wird man feststellen, dass es immer begründete Ausnahmefälle gibt, bei denen man sich nicht an diese Regeln halten sollte.

Um jetzt nicht komplette Verwirrung zu schaffen, sollte man den normalen Ablauf vor Augen halten, wenn man etwas Neues lernt:

Unwissenheit:

Man beginnt etwas und hat die Vorstellung, dass es das Größte ist, was man produziert. Auf Software angewandt handelt es sich dabei um einfache Programme, die gerade mal die einfachsten Business-Anforderungen erfüllen. Der Entwickler denkt aber in dieser Phase, dass niemand so gut programmieren kann wie er selbst.

Frustration:

Nach einiger Zeit wird ein Entwickler merken, dass seine Software doch gewisse Fehler hat und nicht in allen Fällen funktioniert. Durch das Urteil von anderen und die Hinweise auf gewisse akzeptierte Regeln, die im Coding nicht eingehalten sind, wird der Entwickler frustriert sein. In dieser Phase ist es wichtig, dass er nicht einfach aufgibt, sondern weiter lernt. Denn jeder hat einmal diese Phase durchgemacht und versucht, die feststehenden Regeln und die dahinterliegenden Gründe zu lernen.

Anwendung:

Je mehr ein Entwickler lernt, umso mehr gute Regeln werden ihm in seiner täglichen Arbeit begegnen. Nach der Anwendung dieser Regeln sieht sein Coding besser aus und funktioniert fehlerfrei, sodass er weiter darangeht, diese Regeln umzusetzen. Leider ist die Anwendung von Regeln immer sehr zeitaufwendig, sodass die Geschwindigkeit des Programmierens nicht zufriedenstellend ist.

Perfektion:

Im Laufe der Zeit werden die Regeln dem Entwickler in Fleisch und Blut übergehen, und er wird nicht mehr darüber nachdenken müssen, warum er etwas in dieser Weise und nicht in einer anderen macht. Dadurch wird seine Geschwindigkeit beim Programmieren wieder ansteigen und im Prinzip könnte alles in Ordnung sein. Aber wenn er wirklich die Phase der Perfektion erreicht hat, dann wird er anfangen, manche Regeln in gewissen Fällen nicht einzuhalten. Andere Architekten und Entwickler werden ihm vorhalten, dass er die Regeln ja nicht mehr befolgt und dass das Coding deshalb schlecht ist. Seltsamerweise wird in der Realität aber kein Problem damit auftreten, weil es in diesem Fall einfach in Ordnung oder sogar besser ist, die Regel nicht einzuhalten.

Jeder Entwickler will natürlich die Phase der Perfektion erreichen, aber der Weg dorthin ist mit sehr viel Arbeit verbunden, denn er muss schließlich erst die Regeln in dem zu bearbeitenden Bereich verstehen und die Gründe dahinter verinnerlichen.

Dieses Buch soll dazu dienen, ein paar Regeln für ein modulares System aufzustellen, sodass ein Entwickler in der Phase der Anwendung in der Lage ist, ein gutes System zu erstellen. Wenn ein Entwickler allerdings bereits eine gewisse Perfektion erreicht hat, unzählige Regeln gelernt hat, wird er mit Sicherheit Fälle finden, bei denen die Regeln nicht angewendet werden können. Die Regeln sind nur eine Richtschnur für die Implementierung und sollten keinesfalls verbissen verfolgt werden. Es kann immer gute Gründe für eine Abweichung geben.

2 Grundlagen

»Programming without an overall architecture or design in mind is like exploring a cave with only a flashlight: You don’t know where you’ve been, you don’t know where you’re going, and you don’t know quite where you are.«

Danny Thorpe

Um den Weg zu Modularität in einem Softwaresystem zu beschreiben, braucht es zunächst einmal eine gemeinsame Sprache. Wenn man mit Blick auf das Thema Modularisierung die Softwareentwicklung betrachtet, dann tauchen dort immer dieselben Begriffe auf. Um die Konzepte in diesem Buch besser zu verstehen, werden zunächst einmal die Grundbegriffe definiert und erläutert, wie sie zusammenhängen.

2.1 Definitionen

Obwohl dieses Buch den Fokus nicht auf allgemeine Softwareentwicklungsmethoden legt, sondern erklären will, wie das Coding für gewisse Konzepte im Rahmen der Modularität aussieht, sollten einige Grundbegriffe der Architektur definiert sein. Ansonsten kann es zu Missverständnissen kommen.

Zu Beginn sollte der Begriff »Architektur« eindeutig sein. Unter Architektur versteht man die Komponenten eines Systems und ihre Beziehungen zueinander. Es geht um die allgemeine Struktur und Abhängigkeiten von Teilen des Systems. Welche Einheiten es gibt und welche Kriterien für sie gelten, wird Gegenstand der einzelnen Kapitel. Mit Architektur sind nicht die Methoden und Prozesse gemeint, die dazu dienen, die Architektur eines Systems zu erstellen.

Als weiterer, wichtiger Begriff taucht immer wieder »Prinzip« auf. Ein Prinzip ist eine Grundanforderung, die nur postuliert wird. Es gibt keinerlei Herleitung für dieses Prinzip, sondern es handelt sich um ein grundlegendes Designparadigma des Systems. Die Annahme bei einem Prinzip ist, dass die Implementierung besser ist, wenn dieses Prinzip eingehalten wird. Ein Prinzip hat immer ein Ziel, und besser bezieht sich darauf, dieses Ziel durch die Einhaltung des Prinzips zu erreichen.

Ein »Konzept« ist die Anwendung eines oder mehrerer Prinzipien auf die Komponenten des Systems. Dabei geht es hauptsächlich um die Beziehungen zwischen den Komponenten und wie diese Beziehungen aufgebaut werden sollen. Dabei werden für die Komponenten verschiedene Regeln formuliert, die eingehalten werden müssen.

Eine Handlungsanweisung während der Implementierung, die direkt aus einem Konzept folgt, ist ein »Pattern«. Bei einem Pattern wird angegeben, welche Funktion verschiedene Komponenten in ihrem Zusammenspiel übernehmen sollen.

2.2 Grundthese

Die Grundthese des Buches besagt, dass unterschiedliche Ebenen von Architektur existieren. Während eines großen Softwareprojektes sind Entwickler und Architekten früher oder später mit allen Ebenen der Architektur konfrontiert.

Ein Projekt beginnt mit der Systemarchitektur. Architekten skizzieren ein neu zu schaffendes Softwaresystem, indem definiert wird, welche Komponenten notwendig sind und wie sie in die vorhandene Landschaft passen. Es besteht meist kein Zweifel, dass dies die Aufgabe von Systemarchitekten ist.

Nachdem ein ungefähres Verständnis geschaffen wurde, wie das System und seine Umgebung aussehen sollen, wird mehr Fokus auf die Architektur der Komponenten des Systems gelegt. Zu diesem Zeitpunkt wird die Entscheidung getroffen, welche Komponenten gebaut werden und ob von außen Komponenten hinzugenommen werden sollen, weil sie gut zu den Anforderungen der System- und der Komponentenarchitektur passen. Hier ist man zum ersten Mal auf Coding angewiesen, denn normalerweise muss durch Prototypen herausgefunden werden, ob ein Vorschlag umsetzbar ist.

Image

Abb. 2-1 Granularität der Architektur

Im weiteren Verlauf wird das Coding immer wichtiger, denn es geht an die reale Umsetzung des Systems. Auch hier gibt es Architekturkonzepte, um Erfahrungen aus der Vergangenheit zu nutzen.

Alle drei Bereiche, System-, Komponenten- und Coding-Architektur, sind für die Umsetzung eines Systems wichtig, und das Entscheidende ist, dass sie sich gegenseitig beeinflussen. Die beste Systemarchitektur nützt nichts, wenn die Komponentenarchitektur, sprich die Verbindung der einzelnen Komponenten unzuverlässig und störanfällig ist. Genau dasselbe gilt für die Verbindung von Coding-Architektur und Komponentenarchitektur.

2.2.1 Komponenten und Module

Die Begriffe Komponente und Modul sind in der Softwareentwicklung nicht so einfach auseinanderzuhalten. Sowohl eine Komponente als auch ein Modul ist eine abgeschlossene funktionale Einheit einer Software mit einer klar definierten Schnittstelle.

Ein Modul wird hier hauptsächlich als Einheit zur Strukturierung verstanden, dass von einem darunterliegenden Laufzeitsystem zur Strukturierung verwendet wird. Ein Modul kann mehrere Komponenten enthalten. Eine Komponente kann dagegen nicht über mehrere Module verteilt werden.

Bei einer Komponente steht die Logik im Vordergrund, während bei einem Modul der gruppierende Aspekt sehr viel wichtiger ist.

Laut Wikipedia ist auch die umgekehrte Definition möglich [36], d. h., Module setzen Komponenten zusammen. In der Softwareentwicklung sind beide Definitionen vertreten. Im weiteren Verlauf dieses Buches werden allerdings Komponenten immer in Modulen abgelegt.

2.2.2 Selbstähnlichkeit von Architektur

Es mag ein wenig sonderbar klingen, dass die beiden Begriffe Selbstähnlichkeit und Softwarearchitektur zusammen verwendet werden. Dahinter verbirgt sich die Übertragung eines mathematischen Bildes auf die verschiedenen Architekturschichten eines konkreten Systems.

Ein kurzer Ausflug in die Populärwissenschaft soll dies erläutern. Selbstähnlichkeit findet sich in der fraktalen Mathematik. Viele kennen es vielleicht von den Mandelbrotmengen, auch als Apfelmännchen bekannt. Bei der Visualisierung der Mandelbrotmenge stellt man fest, dass sich, sobald man tiefer hineintaucht in die Mandelbrotmenge, die Strukturen wiederholen. Da Architektur die Struktur eines Systems beschreibt, liegt es nahe, dass man mit einem groben Überblick anfängt und tiefer hinabsteigt. Basierend auf den Aussagen zuvor fängt man mit der Systemarchitektur an, geht dann über zu der Komponentenarchitektur, um bei der Coding-Architektur zu landen.

Die Wiederholung der Details bei der Mandelbrotmenge bezeichnet man als Selbstähnlichkeit. Softwareentwicklung verhält sich genauso. Man steigt auf einem hohen Abstraktionsgrad ein und geht weiter hinunter, nur um festzustellen, dass sich die Konzepte und Prinzipien wiederholen. Sollte es einen Bruch dazwischen geben, wäre dieser Bruch früher oder später im System zu bemerken. Ein gutes Beispiel ist das Ziel, ein modulares System mithilfe von Modulen zu implementieren, aber in der Implementierung komplett auf Schnittstellen und Abstraktionen zu verzichten, sodass Klassen andere Klassen unmittelbar referenzieren. Es wäre schwer, ein einziges Modul zu definieren, weil jeder Teil des Codings mit jedem anderen verbunden ist.

Diese Aussage wird im Buch verfolgt, indem aufgezeigt wird, welche Konzepte beim Coding, der Komponentenarchitektur und bei der Systemarchitektur ähnlich sind. Abbildung 2-2 zeigt, wie ein Aspekt der Architektur betrachtet und auf der darunterliegenden Ebene in ähnlicher Weise gefunden wird. Dieselben Prinzipien wiederholen sich.

Image

Abb. 2-2 Selbstähnlichkeit zwischen den Architekturschichten

Um den Problembereich nicht zu groß zu wählen, wird der Fokus auf drei Prinzipien der Architektur gelegt, die für Modularität essenziell sind:

♦ Entkopplung

♦ Einfachheit

♦ Erweiterbarkeit

Bei diesen Themen handelt es sich um nicht-funktionale Eigenschaften eines Systems. Neben den funktionalen besitzt jedes System auch noch Eigenschaften, die nicht unmittelbar als Feature für einen Endanwender sichtbar sind, aber die Architektur zumeist sehr stark beeinflussen. An dieser Stelle soll nicht weiter auf das Konzept der Non-Functional Requirements oder Quality Attributes eingegangen werden [13].

Die Umsetzung dieser drei Eigenschaften auf allen Ebenen des Systems ermöglicht die Implementierung eines modularen Systems. Dadurch ist die Selbstähnlichkeit der Konzepte auf den verschiedenen Ebenen nachgewiesen.

Am Ende jedes Kapitels werden die vorgestellten Konzepte in einer Tabelle dargestellt. Die Tabelle füllt sich mehr und mehr im weiteren Verlauf des Buches und lässt die Ähnlichkeiten offensichtlich werden.

2.2.2.1 Einfachheit versus Erweiterbarkeit und Entkopplung

Wenn man sich diese Prinzipien anschaut und einen ersten Gedanken daran verschwendet, wird jedem sofort einleuchten, dass es natürlich umso besser ist, je einfacher eine Lösung ist. Leider stehen die beiden anderen Prinzipien der Einfachheit manchmal im Wege.

Um Software erweiterbar oder entkoppelt zu entwickeln, müssen meist zusätzliche Abstraktionsschichten eingeführt werden. Jede weitere Abstraktion erhöht aber die Komplexität und steht der Einfachheit entgegen.

Darum ist es umso wichtiger, eine Balance zwischen der Einfachheit auf der einen Seite und der Entkopplung und Erweiterbarkeit auf der anderen Seite zu erreichen. Jedes Konstrukt, das eingeführt wurde, um eine Erweiterung in das System zu bringen, oder eine Zugriffsschicht für eine Komponente sollte hinterfragt werden, ob es wirklich notwendig ist für die Lösung des Problems.

Es gibt keinen immer funktionierenden Ausweg aus diesem Dilemma und – noch schlimmer – manchmal gehen die Meinungen der Entwickler auseinander, ob eine Änderung jetzt die Struktur der Anwendung oder des Systems verbessert oder ob dadurch nur die Komplexität grundlos erhöht wurde. Solange die Entkopplung oder die Erweiterung unmittelbar oder in absehbarer Zukunft benötigt wird, kann man sagen, dass sie notwendig ist. Sollte der Entwickler argumentieren, dass die Anwendung eines Konzeptes in Zukunft notwendig sein könnte, handelt es sich sehr wahrscheinlich um eine unnötige Komplexität.

2.2.3 Schnittstelle und Implementierung

Die Selbstähnlichkeit der verschiedenen Ebenen der Architektur soll an einem sehr einfachen Beispiel bewiesen werden. Anhand dieses Konzeptes wird beschrieben, wie die verschiedenen Ebenen der Architektur den gleichen Prinzipien folgen, sodass Erkenntnisse auf einer Ebene in einer anderen wiederverwendet werden können. Es geht darum, die prinzipielle Gleichheit von Konzepten zu erkennen, um sie dann in anderen Bereichen anzuwenden.

Im weiteren Verlauf des Buches werden etliche Merksätze, Patterns und Prinzipien vorgestellt. Um das Vorgehen zu verdeutlichen, wurde das Konzept gewählt, das die Grundlage für jede Modularität schafft: die Trennung von Schnittstelle (engl. Interface) und Implementierung (engl. Implementation).

Jeder Entwickler kennt den Begriff einer Schnittstelle. Bei einer Schnittstelle handelt es sich um die öffentlich sichtbare Definition einer Funktionalität. Um diese Funktionalität anzubieten, wird üblicherweise eine Implementierung benötigt.

In der modernen Softwareentwicklung wird auf die Separierung von Schnittstelle und Implementierung an den verschiedensten Stellen geachtet. Der Grund für diese Separierung besteht darin, dass die Schnittstellen stabil gehalten werden und sich genau an der zu liefernden Funktionalität orientieren. Die Implementierung dagegen kann geändert werden, ohne dass Anwender der Schnittstelle Umstellungen machen müssen.

Im weiteren Verlauf wird von der Schnittstelle gesprochen, wenn das Konzept eines allgemeinen Vertrages zwischen einem Anwender und einem Anbieter gemeint ist. Wenn es um das direkte Sprachkonstrukt in einer Programmiersprache geht, wird weiterhin der Begriff Interface verwendet.

2.3 Prinzipien und Konzepte

In diesem Abschnitt geht es darum, die grundlegenden Konzepte für die Modularität von Software aufzulisten und jeweils eine kurze Erklärung zu geben. Darüber hinaus werden sie noch in Architekturebenen einsortiert. Zusätzlich lassen sich ihre Beziehungen in Form eines Abhängigkeitsgraphens veranschaulichen.

Image

Abb. 2-3 Abhängigkeit der Konzepte

Die Abhängigkeiten sind in Abbildung 2-3 beschrieben. Die Konzepte sind jeweils mit der Nummer des Kapitels versehen, in dem sie näher beschrieben werden.

Je nachdem, auf welcher Ebene der Architektur sich ein Entwickler gerade bewegt, sollte er sich die dort geltenden Konzepte anschauen und überlegen, wie er sie umsetzen kann. Der Prozess zur Erstellung eines Systems wurde bereits mit der Grundthese kurz beschrieben. Welche Phasen es beim Implementierungsprozess eines modularen Systems gibt, wird im Anschluss näher erläutert.

Design Patterns

Ein weitverbreitetes und anerkanntes Grundlagenwerk der Softwareentwicklung ist das Buch von Gamma, Helm, Johnson und Vlissides [7]. In diesem Buch geht es um Standardprobleme auf der Coding-Ebene in der Softwareentwicklung und wie diese mithilfe von Design Patterns gelöst werden. Die Autoren des Buches haben sich schon beinahe einen mythischen Ruf als »Gang of Four« erworben.

Die dort verwendeten Begriffe sind mittlerweile in den Wortschatz der Entwickler übergegangen. Factory, Adapter, Facade sind dabei neben vielen anderen Begriffen sehr häufig benutzte Konzepte. Mehr Details können dort nachgelesen werden. Im weiteren Verlauf werden noch andere Design Patterns verwendet, sofern sie für das Ziel eines flexiblen, erweiterbaren Systems sinnvoll sind. Es wird darauf verwiesen, wenn es sich um ein solches Design Pattern handelt.

Nicht alle Patterns, die in dem Buch beschrieben werden, sind für ein modulares System relevant, deshalb wird hier nur eine Auswahl beschrieben. Allerdings beinhalten die Design Patterns nicht nur Konzepte, die sich auf Coding anwenden lassen, sondern auch auf die beiden höheren Ebenen der Architektur. Welche Patterns dafür infrage kommen, wird im weiteren Verlauf beschrieben.

SOLID

Bei SOLID handelt es sich um Prinzipien, die im Buch von Robert C. Martin erwähnt werden [3]. Es geht bei den Prinzipien darum, besseren Code zu erzeugen, indem man sich an einfache Regeln hält und die Software stetig umschreibt. In der Softwareentwicklung ist die Gesamtheit folgender Prinzipien als SOLID bekannt:

♦ Single Responsibility

♦ Open Closed

♦ Liskov Substitution

♦ Interface Segregation

♦ Dependency Inversion

Wie man bereits an den Namen erkennt, handelt es sich um Prinzipien. Allgemein werden die SOLID-Prinzipien auf Coding angewendet, aber genau wie bei Patterns eignen sie sich auch für die Anwendung auf den höheren Ebenen der Architektur eines modularen Systems. Die genaue Umsetzung wird in den entsprechenden Kapiteln beschrieben.

Module

Ein Modul ist eine abgeschlossene funktionale Einheit einer Software. So wie Module in diesem Buch aufgefasst werden, müssen sie folgende Kriterien erfüllen:

♦ Definition eines Moduls

♦ Definition der externen Schnittstelle eines Moduls

♦ Definition der Abhängigkeiten eines Moduls

♦ Separierung von Code und Moduldefinition

Solche Module werden in einem modularen Laufzeitsystem ausgeführt, das speziell dafür gedacht ist, die Regeln von Modulen einzuhalten. Jede Implementierung innerhalb eines modularen Systems ist einem Modul zugewiesen.

Services

Ein Service bündelt Funktionalität und bietet sie für andere Anwender an. Jeder Service besitzt eine klar definierte Schnittstelle, sodass für die Benutzung keinerlei Informationen über die Implementierung notwendig sind.

Services sollten in einem Verzeichnis registriert sein und können zur Laufzeit gebunden werden. Damit ist der Anbieter vom Anwender eines Service entkoppelt.

Module exponieren Services als Kommunikationsmittel, um mit anderen Modulen Daten auszutauschen. Das modulare Laufzeitsystem stellt das Verzeichnis zur Verfügung.

Schnittstelle

Eine Schnittstelle ist in diesem Zusammenhang die öffentlich zugängliche Funktionalität eines Moduls, gekapselt als ein Service. Eine Schnittstelle kann weiterentwickelt werden, aber nur im Rahmen der Kompatibilität, sodass der Vertrag zwischen einem Anwender und einem Anbieter nicht verletzt wird. Sobald beide nach einer Änderung den Code anpassen müssen, ist das Prinzip der Entkopplung nicht erfüllt.

Schnittstellen werden eingesetzt, um Module voneinander zu entkoppeln und klare Verwendungsbeziehungen zwischen den Teilnehmern aufzustellen.

Dependency Injection

In der klassischen Softwareentwicklung ist die Verwendungsbeziehung zwischen einem Anbieter und einem Anwender so gelöst, dass der Anwender selbst die erforderlichen Instanzen erzeugt, sobald sie benötigt werden. Mit dieser direkten Abhängigkeit können aber Anwender und Anbieter niemals voneinander entkoppelt werden.

Beim Dependency-Injection-Konzept vertraut ein Anwender darauf, dass ein Framework alle Instanzen bereits vorher erzeugt, sodass die Abhängigkeit nur noch injiziert werden muss. Die direkte Abhängigkeit zwischen einem Anwender und einem Anbieter ist aufgebrochen.

Die Aufgabe zur Erzeugung von zu verwendenden Instanzen kann vom modularen Laufzeitsystem erfüllt werden. Im Allgemeinen handelt es sich bei den Instanzen um Services, die eine Schnittstelle anbieten.

Schichten

Wenn die Software in Module aufgeteilt ist und die Schnittstellen zwischen den Modulen sehr genau definiert sind, stellt sich immer noch die Frage, wie die Module in einem System aufgeteilt sind. Im Laufe der letzten Jahre hat sich durchgesetzt, dass die Module jeweils einer Schicht zugewiesen werden. Dabei sind die Regeln für eine Schicht sehr einfach:

♦ Ein Modul in einer Schicht darf nur Abhängigkeiten zu Modulen in tieferen Schichten haben.

♦ Abhängigkeiten zu Modulen derselben Schicht oder zu einer höheren Schicht sind nicht erlaubt.

Schichten unterstützen das Design eines Systems und geben einen Rahmen vor, in den die Module eingepasst werden können.

Gemeinsam genutzte Komponenten

Neben der Wiederverwendung von Modulen über die Schnittstellen der Programmiersprache gibt es noch eine Reihe anderer Sprachmittel wie Annotationen oder aspektorientierte Programmierung, die dazu geeignet sind, Teile des Codings in separate Module auszulagern und somit wiederzuverwenden.

Dabei stellen sich mitunter Probleme bezüglich des Kontextes, der notwendig ist, um das Coding-Fragment wiederzuverwenden.

Entkopplung externer Komponenten

Externe Komponenten sind für jede reale Softwarelösung notwendig, aber leider erfüllen sie meistens nicht die Anforderungen an Module in einem modularen Laufzeitsystem. Die Abhängigkeiten und die Schnittstellen sind nicht genau definiert.

Wenn die Datenstrukturen innerhalb des gesamten Systems verwendet werden, führt das zu einer zu engen Kopplung zwischen den Modulen und den externen Komponenten. Eine Weiterentwicklung wird dadurch sehr erschwert.

Deshalb bietet es sich an, für alle externen Komponenten einen Proxy zu implementieren, um die Abhängigkeit zumindest an einer Stelle zu bündeln.

Ersetzbarkeit

Durch die Trennung von Schnittstelle und Implementierung übernimmt die Schnittstelle die Aufgabe eines Vertrages zwischen Anwender und Anbieter. Neben der besseren Strukturierung ergibt sich noch der Vorteil, dass die Implementierungen ersetzt werden können, solange sie dieselbe Schnittstelle erfüllen.

Bei der Ersetzbarkeit handelt es sich um ein sehr wichtiges Prinzip in der Modularität, denn erst durch die wirkliche Ersetzbarkeit von Modulen basiert das System auf festgeschriebenen Verträgen und nicht auf Absprachen zwischen den Entwicklern, die während der Implementierung getroffen wurden.

Parallele Verarbeitung

Parallele Verarbeitung benötigt einen weiteren Schritt, um Modularität zu erreichen. Wenn Aufgaben aufgeteilt werden können, bedeutet das die Modularisierung der Aufgaben.

Diese Trennung geht über die Anforderungen an ein modulares Laufzeitsystem hinaus, aber parallelisierbare Aufgaben sind nichtsdestotrotz Objekte, die einer klaren Schnittstelle, einem Vertrag, genügen müssen.

Versionierung

Sobald in einem modularen System Ersetzbarkeit von Modulen erreicht ist, stellt sich unmittelbar die Frage, welche Art von Modulen ersetzt werden können. Zunächst einmal gilt die Ersetzbarkeit für Module, die eine unveränderte Schnittstelle implementieren.

Aber Systeme werden weiterentwickelt. Modulare Systeme sind erst recht dafür ausgelegt, dass sie über einen langen Zeitraum an neue Anforderungen angepasst werden können. Die unmittelbare Konsequenz daraus ist die Versionierung von Modulen. Neben der Anforderung, dass sich die Schnittstellen von Modulen nur kompatibel ändern können, ergibt sich die Anforderung, verschiedene Versionen gleichzeitig ausführen zu können.

Repository

Bisher wurde nur das modulare System selbst betrachtet, aber in der Anforderung für die Versionierung stellt sich die Frage, woher die neuen Module kommen. Daraus ergibt sich die Anforderung, dass Module in allen Versionen in einem Repository abgelegt werden, sodass die angeforderten Versionen direkt aus diesem Repository gezogen werden können.

Damit ist das Repository zwar nicht zu einem notwendigen Bestandteil eines modularen Systems geworden, aber es ergänzt die Modularität.

Assembly

Sobald die Software in einem modularen System alle Kriterien erfüllt, ist es möglich, dass die Module unterschiedlich zusammengestellt werden. Im besten Fall ergeben sich daraus unterschiedliche Produkte, die aber immer noch einen sehr großen Anteil gleicher Module verwenden.

2.4 Modularity Patterns

Die Konzepte und Prinzipien sind die allgemeinen Grundlagen, die für ein modulares System gelten müssen. Leider ist es unter Umständen sehr schwer, genau zu bestimmen, wie sich welches Konzept in der konkreten Implementierung bezüglich Modularisierung ausprägt. Um diesen Schritt einfacher zu machen, soll an dieser Stelle ein Überblick über die wichtigsten Modularity Patterns gegeben werden. Der Name ist bewusst gewählt, denn in der Softwareentwicklung wird der Begriff eines Patterns im Standardwerk von Gamma, Helm, Johnson und Vlissides [7] für Struktur im Coding verwendet. In diesem Buch beziehen sich Design Patterns auf alle drei Ebenen der Architektur, auch auf Komponenten und das gesamte System.

Im Verlauf der Kapitel werden die wichtigsten Patterns in Merksätzen hervorgehoben. Die Beschreibungen werden sich auf das absolute Minimum beschränken, denn die Erklärungen befinden sich direkt in den einzelnen Kapiteln. Die Merksätze dienen nur als Katalog für die spätere Anwendung in konkreten Projekten und zum leichteren Auffinden. Sie sind stets gleich aufgebaut. Einer Beschreibung folgt jeweils eine Auflistung der notwendigen Komponenten.

2.4.1 Modularity Patterns in der Coding-Architektur

Um ein modulares System zu erreichen, sind auf der Coding-Ebene Modularity Pattern sehr hilfreich.

Factory - Using factories for object instantiation:

Eine Factory entkoppelt die Erzeugung einer Objektinstanz von der Verwendung (Seite 44). Dieses Pattern ist direkt aus den Design Patterns von Gamma, Helm, Johnson und Vlissides [7] entnommen.

Code module - Defining code modules to decouple the implementation:

Direkte Sprachkonstrukte ermöglichen die Einfachheit der Implementierung, besonders bezüglich Erweiterbarkeit und Entkopplung (Seite 47).

Acyclic dependencies - Avoiding cyclic dependencies in coding:

Zyklen in der Implementierung verhindern modulare Systeme aufgrund der engen Kopplung der Klassen (Seite 92). Hierbei handelt es sich um ein Grundprinzip von Modulen, das aber nur durch entsprechende Gestaltung des Codings zu erreichen ist.

Extensibility with DTO - Using Data Transfer Objects (DTO) for extensibility:

Ungetypte Datenobjekte in Schnittstellen erlauben syntaktisch korrekte Erweiterbarkeit (Seite 170). Um Erweiterbarkeit zu erreichen, bietet es sich manchmal an, auf die Typisierung zu verzichten, denn ein DTO kann natürlich auch getypt für den Transfer zwischen Komponenten verwendet werden.

Extensibility with abstract classes - Using abstract classes for extensibility:

Abstrakte Klassen in Schnittstellen erlauben die Verwendung eines SPI (Service Provider Interface) mit minimalem Aufwand (Seite 169). Erweiterbarkeit basiert darauf, dass sich die Implementierungen eines SPI immer gleich verhalten (d. h. austauschbar sind), daher sind Defaultimplementierungen ein probates Mittel.

2.4.2 Modularity Patterns in der Komponentenarchitektur

Die Komponenten innerhalb der Module sollten gewissen Modularity Patterns folgen, um Modularität im System zu erreichen.

Runtime binding - Binding components late at runtime:

Die Zusammenstellung der Komponenten zur Laufzeit ermöglicht Flexibilität innerhalb eines modularen Systems (Seite 76). Ein modulares Laufzeitsystem unterstützt dieses späte Binden, indem die Schnittstellen eingehalten werden und die Kommunikation komplett über Services funktioniert.

Module facade - Defining a facade for every module:

Ein Modul in einem modularen Laufzeitsystem muss eine klare Schnittstelle definieren (Seite 82). Bei einer Facade handelt es sich um ein Design Pattern aus Gamma, Helm, Johnson und Vlissides [7]. Aber nicht nur auf Coding-Ebene muss es eine klare Schnittstelle geben, sondern erst recht auf Modulebene.

Modular adapter - Bridging between components by modular adapters:

Komponenten können über eine weitere Komponente zusammenarbeiten, ohne dass beide geändert werden müssen (Seite 85). Ein Adapter ist wiederum ein Design Pattern von Gamma, Helm, Johnson und Vlissides [7], aber es kann auch auf Module angewendet werden, um die Komponenten in einem modularen Laufzeitsystem miteinander kommunizieren zu lassen.

Extensions - Restricting extensibility mechanism for modules:

Ein Framework darf nur einen Mechanismus beim Hinzufügen von Funktionalität anbieten (Seite 107). Bei den SOLID-Prinzipien existiert das Prinzip, dass ein Objekt nur für eine Aufgabe verantwortlich sein soll (Single Responsibility). Eine Abwandlung dieses Prinzips ist die Umsetzung für Erweiterungspunkte.

Module services - Using services as communication means between modules: