Der objektorientierte Entwurf und die OO-Programmierung brachten eine Verheißung in die Szene der 80er Jahre: Information Hiding. Daten sollen nur dort gehalten werden, wo man sie wirklich braucht, und nur die passende Logik soll Zugriff darauf haben. Beides wird zusammen in einem Objekt untergebracht. Ein wohldefiniertes Interface stellt die Operationen nach außen zur Verfügung. Mit einem solchen Softwaredesign steht und fällt die Wartbarkeit einer jeden Anwendung.
Doch zwei Jahrzehnte später lassen viele Änderungen, Bugfixes und Release-Zyklen von den ursprünglichen Ideen und Strukturen aus der Erstellungsphase kaum noch etwas erkennen. Es sind Softwaresysteme entstanden, die aus Tausenden von Klassen und zur Laufzeit aus Millionen von Objekten bestehen. Darüber hinaus kann prinzipiell jedes Objekt die Schnittstelle jedes anderen benutzen. Kein Entwickler ist in der Lage, hier die Übersicht zu behalten.
Die Schaffung von Modulen, auch im Hinblick auf Service-orientierte Architekturen, gilt deshalb als guter Stil. In den OO-Sprachen des Mainstreams wird das jedoch nur durch schwache Sprachkonstrukte unterstützt. So ist zum Beispiel das Private/Protected/Public-Rechtesystem in Java auf Packages beschränkt, die ihrerseits keine sinnvolle Größenordnung für ein Modul darstellen. In Modula-3 oder Oberon ist das besser gelöst, aber wer benutzt das schon? Die Folge: Änderungen an einer Stelle haben potenziell Einfluss auf viele andere Teile einer Anwendung. Entwickler müssen dies entweder nachvollziehen, was erhöhten Aufwand bedeutet, oder sie können dies gar nicht leisten, und das verringert die Qualität.
Die Lösung für dieses konkrete Problem ist klar: Ein Modulbegriff muss her und auch durchgesetzt werden. Genau hierfür bietet OSGi die geeigneten Mittel: Ursprünglich für die automatische nachträgliche Installation (Provisioning) in Embedded-Systemen gedacht, macht es herkömmliche Java-Bibliotheken (JARs) zu Modulen mit gekapseltem Innerem und definierter Schnittstelle.
Was ist OSGi?
Die Open Services Gateway Initiative spezifiziert eine Java-Plattform, auf der Module und Dienste verwaltet werden können.
-
Module = "Bundle": ein JAR (Java Archive), dessen Manifest spezifiziert, welche Pakete von außen gesehen werden.
-
Bundles können einer laufenden Anwendung nachträglich hinzugefügt werden (Hot Deployment).
-
Bundles können gleichzeitig in verschiedenen Versionen vorliegen und benutzt werden.
-
Dienste: Java-Klassen können als Dienst registriert und dann von jedem Bundle aus gefunden und aufgerufen werden.
-
Die aktuelle Version 4.2 enthält mit der Enterprise-Specification auch Dienste, die die Plattform bereitstellen muss, zum Beispiel Bundle-Verwaltung, Rechte, Logging, Remoting, http-Service, Konfiguration, Benutzer, JTA, JMX, JNDI, JDBC und XML Parser.
Zwei Wege bei der Modularisierung
Der Unterschied zum eingangs erwähnten Information Hiding auf Objektebene liegt in der Granularität. Entwickler sind nun gehalten, die gesamte Anwendung in Module zu unterteilen, deren gegenseitige Nutzung vergleichsweise einfach strukturiert ist. Während man dies auf der grünen Wiese von vornherein berücksichtigen kann - und hoffentlich auch ohne OSGi bereits getan hat -, ist die Modularisierung einer Altanwendung eine größere Herausforderung. Entgegen der häufig zu beobachtenden Praxis, eine große Anwendung gleich zu Beginn in viele kleine Teile zu zerlegen, bis sie letztlich völlig destabilisiert ist, raten wir zu einem Vorgehen in kleinen Schritten. Generell wählt man dafür ein iteratives Vorgehen, trennt also Modul um Modul aus der Anwendung heraus, definiert seine Schnittstelle und prüft nach jedem Schritt die korrekte Funktion der Anwendung.
Dabei muss klar sein, dass die Modularisierung zur Laufzeit ein OSGi-Container übernimmt, in dem die Module (OSGi-Jargon: "Bundles") installiert werden. Es gibt also prinzipiell zwei Möglichkeiten, die Modularisierung vorzunehmen:
1. Container-in-App:
Bei dieser Vorgehensweise wird ein zunächst leerer OSGi-Container in die Altanwendung eingebunden, und anschließend wird getestet, ob die Anwendung in dieser Konfiguration problemlos startet. Ist das der Fall, beginnt man schrittweise, kleine Funktionen aus der Anwendung zu isolieren und als Bundle in den Container zu packen. Nach jedem dieser "Umzüge" ist ein Test angeraten. Da OSGi selbst keinen Aufruf der Anwendungs-Bundles von außerhalb des Containers vorsieht, muss für die Kommunikation von Altanwendung und Bundle ein Dispatcher-Dienst im Container vorhanden sein. Er sorgt dafür, dass ein Aufruf von der Altanwendung OSGi-konform an ein Container-Bundle delegiert wird. Entsprechend sind die Aufrufe der Altanwendungsrümpfe zu ändern und die isolierten Bundles mit einer Schnittstelle zum Dispatcher-Dienst zu versehen.
2. App-in-Container:
Auf einem etwas radikaleren Weg kann man die gesamte Altanwendung zu einem großen Bundle schnüren und in den Container packen - der eigentlich von OSGi vorgesehene Ansatz. Das Anwendungs-Bundle startet daraufhin die Anwendung wie gewohnt. Der Anwender startet statt der Anwendung den Container, was typischerweise in Start-up-Skripten verborgen wird.
Anschließend beginnt die eigentliche Arbeit, indem Anwendungscode aus dem großen Bundle extrahiert und als weiteres Bundle installiert wird.
Während der erste Ansatz die Installationsumgebung unangetastet lässt, ist der zweite mit weniger Aufwand für die Kommunikation des Altanwendungsrumpfes mit den Bundles verbunden. In beiden Fällen erhält man ein Geschenk: die einfache Erweiterbarkeit der Anwendung durch Plug-ins. Dies gilt insbesondere, wenn man sich für den OSGi-Container Equinox aus dem Eclipse-Projekt entscheidet. Außerdem gibt es eine Reihe von Standarddiensten, die ein OSGi-basierendes Projekt nutzen kann: Remoting, Logging und Benutzerverwaltung sind nur einige Beispiele.
Eclipse Equinox
Die beliebte Java-Entwicklungsumgebung Eclipse bringt ihren eigenen OSGi-Container mit. Zusätzlich bietet er zum Beispiel:
-
Plug-ins: Module können "Extensions" deklarieren, die die Anwendung abfragen und aufrufen kann.
-
Module können "Buddy"-Modulen tiefere Einsichten erlauben.
-
Mit P2 steht eine mächtige Provisioning (Installations- und Update)-Umgebung zur Verfügung.
Fallstricke
Bei diesem Vorgehen gilt es allerdings, einige Fallstricke zu beachten. Wir haben die skizzierte Blaupause auf eine größere Applikation angewendet: Das quelloffene ERP-System ADempiere ist umfangreich und glänzt durch bestechende fachliche Features sowie einige architektonische Geniestreiche, ist jedoch nicht unbedingt für sauberes Softwaredesign oder Erweiterungsfähigkeit bekannt. Diese Schwächen wollten wir abstellen und haben dafür beide Vorgehensweisen ausprobiert.
Während die Umstellung des Swingbasierenden Fat Client zunächst nach Lehrbuch funktionierte, traten im weiteren Verlauf in beiden Ansätzen einige Probleme zutage.
-
Verwendete Bibliotheken: Jede verwendete Bibliothek muss entweder selbst in ein Bundle gewandelt (hierfür gibt es ein geeignetes Werkzeug) oder - ungern - im verwendenden Bundle installiert werden (Bundles lassen, anders als normale JARs, enthaltene Archive zu). Die Zahl der Bundles im Container steigt dadurch sprunghaft an.
-
Automatische Builds: Sie gehören zum guten Ton, die Nightly Builds auf einem entsprechenden Build-Server. Kommt Eclipse Equinox zum Einsatz, wird die sehr gute Entwicklungsumgebung zum Problem, da sie anstelle automatischer Tests aus dem Stand heraus jeweils einen Testaufbau über grafische Werkzeuge vorsieht, was durchaus komfortabel ist, aber zusätzlichen Aufwand bedeutet. Ant4eclipse liefert hier eine gute Lösung.
-
WebStart: Prinzipiell unterstützt zum Beispiel Equinox den Start des Containers mit dieser Technik. Allerdings müssen die herunterzuladenden Plug-ins explizit konfiguriert werden (in Form so genannter Features) - kein ganz einfaches Verfahren, und Hot Deployment (siehe Kasten "Was ist OSGi?") ist damit nicht möglich.
-
Web-Anwendungen: Hier stellt sich die Container-gegen-Container-Frage: OSGi-Container im JEE-Container oder JEE-Container im OSGi-Container? Die erste Variante wird durch so genannte Bridges unterstützt. Tatsächlich stellen diese aber noch nicht den gesamten Umfang der Servlet-Spezifikation bereit - und plötzlich kann man zum Beispiel keinen Filter installieren. Für den - immerhin auf dem komplexen Web-Framework ZK basierenden - Web-Client von ADempiere reichte es aber. Die zweite Variante wird durch Tomcat- oder Jetty-Bundles möglich. Das funktioniert, hat aber eine deutliche Änderung der Installationsumgebung zur Folge. Für Spring-basierende Projekte und solche, die es werden wollen, ist Spring-DM eine Alternative.
-
EJB-Anwendungen: Die Server-Technik von JEE markiert derzeit das Ende der Fahnenstange. Zwar arbeiten verschiedene Projekte und Anbieter (zum Beispiel JBoss, Easybeans und Websphere) daran, ihren JEE-Container OSGi-fähig zu machen. Wer sich jedoch mit Betaversionen keine blutige Nase holen möchte, der muss seine Anwendung umschreiben. Für das Transaktionsverhalten gibt es heute allerhand Alternativen, Remoting kann mit OSGi Remote Services oder flexibler zum Beispiel mit Eclipse Riena erreicht werden. Leichtgewichtiger wird der Server damit auch in Zeiten von EJB 3 allemal.
Fazit und Links
Die Modularisierung einer Altanwendung bekommt man auch mit OSGi nicht geschenkt. Reine Entwicklungsrichtlinien und konsequente Code-Reviews mögen das Gleiche ausrichten. Sind jedoch auch einfache Erweiterbarkeit und ein Plug-in-Konzept gefragt, sollte Eclipse Equinox in die engere Wahl gezogen werden. (ue)