Kommen wir zum Geschäft

OSGi für Enterprise-Applikationen

Alexander Grzesik

©Shutterstock/EDHAR

Wenn andere Entwickler hören, dass man Enterprise-Anwendungen mit OSGi entwickelt, löst das oft überraschte Reaktionen aus. Viele verbinden OSGi mit der Entwicklung von Embedded Systems, und es hat den Ruf, kompliziert und schwer benutzbar zu sein. In diesem Artikel wollen wir aufzeigen, dass OSGi gerade für die Entwicklung von geschäftskritischen Anwendungen eine Reihe von Vorteilen bietet und die Entwicklung nicht komplizierter ist als mit anderen Frameworks wie Java EE oder Spring, wenn man einige Dinge beachtet. Gleichzeitig zeigen wir typische Fallstricke auf und beschreiben, wie diese zu umgehen sind.

Das Grundparadigma von OSGi ist Modularität: ein Konzept, das seit Jahrzehnten als Grundpfeiler für gute Programmierung und saubere Softwarearchitekturen gilt. Eine Software in Module aufzuteilen, soll vor allem zwei Dinge erreichen. Zum einen soll jedes Modul einem definierten Zweck dienen und dadurch eine klare Trennung der Zuständigkeiten erreicht werden (High Cohesion). Zum anderen sollen Module über definierte Schnittstellen miteinander kommunizieren (Low Coupling). Warum also gilt OSGi als kompliziert? Das liegt vor allem daran, dass OSGi Modularität einfordert. Das ist für den Einsteiger zunächst einmal ungewohnt und erfordert ein gewisses Maß an Disziplin bei der Entwicklung. Dabei liegen gerade in dieser strikten Modularität viele Vorteile für die Entwicklung von Geschäftsanwendungen. Dazu gehören zum Beispiel:

  • Bessere Wartbarkeit
  • Erhöhte Wiederverwertbarkeit
  • Reduktion von Testaufwänden
  • Geringe Down-Zeiten für Bugfixes

Um aufzuzeigen, wie das zu erreichen ist, machen wir einen kurzen Exkurs in die Funktionsweise von OSGi.

Modularität nach Art des Hauses

Modularität bedeutet, sich stärker Gedanken über Struktur und APIs zu machen, bevor und während man entwickelt. Dies klingt erst einmal selbstverständlich, es erfordert aber eine hohe Disziplin, sich daran auch zu halten. Nicht mehr alle public-Klassen oder Methoden sind in der gesamten Applikation verfügbar, sondern nur solche, die ein Modulentwickler wirklich nach außen verfügbar machen möchte. In OSGi wird ein Modul als Bundle bezeichnet. Ein Bundle ist ein normales JAR-File, das mit Metadaten zu seinen Abhängigkeiten angereichert und von einem eigenen Bundle Class Loader geladen wird. Dieser Class Loader lädt ausschließlich Klassen in seinem Bundle. Wenn ein Bundle ein Package aus einem anderen Bundle importiert, wird das Laden von Klassen aus dem jeweiligen Package an den Bundle Class Loader des anderen Bundles oder den System-Class-Loader delegiert (Abb. 1). Jedes Bundle wird in der OSGi Runtime ausgeführt und verfügt im Gegensatz zu einem statischen JAR-File über einen Lebenszyklus, in dem es dynamisch installiert, gestartet, gestoppt, aktualisiert und deinstalliert werden kann (Abb. 2).

Abb. 1: OSGi Class Loading

Abb. 1: OSGi Class Loading

Abb. 2: OSGi-Bundle-Lifecycle

Abb. 2: OSGi-Bundle-Lifecycle

In der Praxis sieht das Erstellen der Metadaten so aus, dass die Manifest-Datei im JAR-File um entsprechende Attribute ergänzt wird (Listing 1). Diese Daten manuell zu pflegen, wäre zeitaufwändig und fehleranfällig. Zum Glück liefert das Bnd-Projekt einen Werkzeugkasten für OSGi-Entwickler, um die Generierung der Bundle-Metadaten zu automatisieren. Bnd analysiert die Klassen eines Bundles und kann Import- und Exportstatements automatisch erzeugen. Darüber hinaus unterstützt es einen Entwickler bei der Versionierung seiner Packages und Bundles. Für Bnd gibt es mit den Bndtools eine sehr gute Integration in Eclipse (siehe dazu auch Eclipse Magazin 3.2015, S. 17) oder, wer Maven nutzt, das auf Bnd basierende Maven-Bundle-Plug-in vom Apache-Felix-Projekt, das sich ebenfalls in Eclipse mit der M2E-Erweiterung nutzen lässt.
Durch die Verwendung von Bnd wird einer der größten Kritikpunkte an OSGi, die Komplexität bei der Bundle-Erzeugung, entschärft und die Metadatenerzeugung stark vereinfacht. Der OSGi-Entwickler kann sich auf die Programmierung der Businesslogik konzentrieren und muss die Metadaten gegebenenfalls nur noch justieren.

Bei der Implementierung von Bundles sollte immer das Prinzip der „High Cohesion“ beachtet werden. Jedes Bundle sollte eine spezielle Aufgabe haben und alles dafür Notwendige bereitstellen. Man sollte vermeiden, „Multifunktions-Bundles“ zu erzeugen, die viele verschiedene Aufgaben erfüllen, da dadurch die Idee der Modularität untergraben wird.

Wichtig bei der OSGi-Entwicklung ist es, auf eine korrekte Versionierung zu achten. Alle OSGi Bundles sollten den Regeln des Semantic Versioning folgen. Diese Regeln sind einfach und klar definiert, führen bei Nichtbeachtung aber zu Problemen für die OSGi-Laufzeitumgebung beim Versuch, die Abhängigkeiten aus Imports korrekt aufzulösen. Dies kann auch Auswirkungen auf die Auswahl der zu nutzenden Bibliotheken in einem Projekt haben. Einige verbreitete Projekte, wie z. B. die Google-Commons-Bibliotheken, erhöhen mit jedem Release die Major-Version, was deren Einbindung in OSGi-Projekte schwierig macht. Auch bei der korrekten Versionierung der eigenen Bundles unterstützen uns die Bnd-Werkzeuge durch eine so genannte Baseline-Funktion, die Änderungen seit dem letzten Release prüft und Vorschläge zur Anpassung der Versionsnummer macht.


Bundle-Name: PaaS+ Sample - PetClinic - API
Bundle-SymbolicName: com.cloudyle.paasplus.samples.petclinic.api
Bundle-Version: 2.0.0
Export-Package: com.cloudyle.paasplus.petclinic;version="2.0.0";
       uses:=" com.cloudyle.paasplus.petclinic.persistence"
Import-Package: com.cloudyle.paasplus.petclinic.persistence;version="[2.0,3)"
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.7))"


Das OSGi-Dienstemodell

Neben dem Bundle-Konzept ist der zweite wichtige Baustein für die Modularität von OSGi das Dienstemodell. Ein OSGi-Dienst (Service) besteht aus einem API und (mindestens) einer Implementierung (Provider). Die Aufgabe von Diensten ist es, Bundles mithilfe des Whiteboard-Pattern zu entkoppeln. Sie werden in der OSGi-Service-Registry verwaltet. Ein Dienstnutzer muss nur das API kennen und hat keine Abhängigkeiten auf die Implementierung. Dies hat den Vorteil, dass OSGi-Dienste leicht austauschbar und erweiterbar sind und dies sogar zur Laufzeit, ohne dass die Dienstnutzer davon beeinträchtigt werden. Das Dienst-API ist ein einfaches Java-Interface. Der Provider registriert sich dann als eine Implementierung für dieses Interface. Ein Pluspunkt dabei ist, dass sowohl die Implementierung des API als auch des Providers keinerlei OSGi-spezifischen Code enthalten muss. Durch Einsatz entsprechender Komponentenframeworks wie Blueprint oder Declarative Services, beides sind OSGi-Spezifikationen, kann der ganze Prozess der Serviceregistrierung und -nutzung sehr einfach in eine Anwendung integriert werden. Ein Beispiel für die Anwendung von OSGi-Diensten mit Blueprint steht mit unserem PetClinic-Tutorial zur Verfügung.

Mit OSGi-Diensten wird die zweite Forderung an modulare Anwendungen erfüllt, die lose Kopplung (Low Coupling). Kleine spezialisierte Bundles, die OSGi-Dienste über ein definiertes API bereitstellen, sind leicht wieder zu verwenden und auszutauschen. Das erinnert nicht von ungefähr an den aktuellen Architekturtrend, weg von großen monolithischen Diensten hin zu einer Microservice-Architektur mit vielen kleinen spezialisierten Diensten. OSGi verfolgt diese Konzepte schon seit über einem Jahrzehnt, und beides funktioniert hervorragend zusammen. Wenn man seine OSGi-Dienste mit einem REST-API versieht, können diese durch die Dynamik, die OSGi zur Verfügung stellt, sehr einfach für den Aufbau einer solchen Architektur genutzt werden.

Dantes Inferno

Wer Java-Enterprise-Anwendungen entwickelt, ist es gewohnt, dass er alle Klassen nutzen kann, die im Projekt mitgeliefert werden und im Classpath zu finden sind. Das ist komfortabel, führt aber leicht dazu, dass in großen Projekten viele „unsichtbare“ Abhängigkeiten zwischen Paketen entstehen, die die Wartung und das Verständnis solcher Anwendungen sehr erschweren. Ein weiterer Punkt, der in der Praxis oft zu Problemen führt, deren Ursache schwer zu ermitteln ist, stellt das „Class Shadowing“ dar. Das klassische Java-Class-Loading funktioniert linear, d. h., alle Bibliotheken im Classpath werden der Reihe nach geprüft, ob eine bestimmte Klasse enthalten ist. Sobald diese gefunden wird, endet die Prüfung – der erste Fund wird fortan genutzt. Im Classpath kann die Klasse aber durchaus noch in anderen Bibliotheken in anderen Versionen vorhanden sein, und die falsche Version wird zuerst gefunden. Gerade wenn man viele externe Bibliotheken benötigt, die wiederum eigene Abhängigkeiten haben, kann dies schwerwiegende Probleme verursachen. Oder man merkt erst zur Laufzeit, dass eine bestimmte Funktion eine Bibliothek benötigt, die gar nicht in der Applikation mitgeliefert wird. Für diese und weitere typische Class-Loading-Probleme in Java hat sich der Begriff „JAR-Hölle“ geprägt.

Mit OSGi erkennt man solche Abhängigkeiten schon während der Entwicklung oder spätestens beim Deployment, wenn Abhängigkeiten nicht aufgelöst werden können. Leider ist es nicht immer ganz so einfach, denn der Teufel steckt im Detail, und wenn man nicht aufpasst, kommt man von der JAR- direkt in die Bundle-Hölle. Wenn Semantic Versioning bei der Versionierung der Bundles nicht beachtet wird, bringt das die OSGi-Laufzeitumgebung schnell aus dem Takt, und man hat mit mehrfachen Abhängigkeiten zu kämpfen, was sich durch Dependency-Chain-Exceptions beim Deployment manifestiert. Problematisch kann auch die Nutzung von Singletons und statischen Variablen sein. Da jedes Bundle seinen eigenen Class Loader hat, hält jedes Bundle unter Umständen auch eigene Instanzen eines Singletons. Bei der Nutzung von Singletons sollte man daher darauf achten, im gleichen Bundle-Kontext zu bleiben.

Schwierig wird es auch bei Verwendung von Class.forName() oder dem Java-Service-Loader. Beides sucht Klassen abhängig vom Class Loader, was mit der OSGi-Class-Loader-Hierarchie teilweise nicht zusammen arbeitet und beim Erzeugen der Bundle-Header über Bnd nicht berücksichtigt wird. Wenn möglich, sollte man beides im OSGi-Kontext vermeiden oder zumindest nicht über Bundle-Grenzen hinaus verwenden.

Vorteile für Geschäftsanwendungen

Wenn man sich an einige Best Practices hält und die richtigen Werkzeuge einsetzt, dann ist die Entwicklung mit OSGi nicht komplizierter als mit anderen Frameworks wie Java EE oder Spring. Die Hintergründe des OSGi-Class-Loadings zu verstehen, ist dabei ebenfalls wichtig, dann lassen sich Fallstricke meist einfach vermeiden. Dafür gewinnt man bei der Entwicklung von Geschäftsanwendungen eine Reihe von Vorteilen. Zum einen sind OSGi Bundles kleine unabhängige Komponenten, die sich durch die definierten APIs einfach wieder verwenden lassen. Vor allem die Dynamik zur Laufzeit und die Möglichkeit, verschiedene Versionen der gleichen Bibliotheken parallel zu betreiben, sind große Stärken von OSGi. Damit wird es einfach, verschiedene Programmteile unter einen Hut zu bekommen, selbst wenn diese unterschiedliche Bibliotheksversionen benötigen. Gerade bei größeren Refactoring-Aufgaben ist das ein nicht zu unterschätzender Vorteil. Man kann Teile eines Projekts auf ein neues API oder eine neue Technologie umstellen, während andere Teile noch zeitweise die alten Versionen nutzen.

Bugfixes können auf ähnliche Art und Weise zur Laufzeit eingespielt werden, ohne Unterbrechung des Systembetriebs. Das ist für kritische Geschäftsanwendungen ein gewaltiger Mehrwert. Das Vorgehen dabei nutzt die Entkopplung von Bundles durch OSGi-Dienste. Betrachten wir dazu folgendes Beispiel (Abb. 3):

  • In einem Bundle X in der Version 1.0 wird ein Fehler gefunden, der mit der Version 1.1 behoben wird. Das Bundle stellt einen Service A bereit, der von verschiedenen anderen Bundles referenziert wird.
  • Das neue Bundle X in der Version 1.1 wird parallel eingespielt. Wenn nun ein Bundle den Service X anfragt, erhält es die Referenz auf die Version 1.1, während „ältere“ Bundles die Referenz auf 1.0 behalten.
  • Nun kann Bundle X 1.0 deinstalliert werden. Alle Bundles, die den Service A in der Version 1.0 nutzen, führen ihre Operationen zu Ende aus und verlieren dann die Referenz auf die Version 1.0. Wenn sie das nächste Mal den Service A anfragen, wird automatisch der neue Service in Version 1.1 zugewiesen und verwendet.
Abb. 3: OSGi Bugfixes

Abb. 3: OSGi Bugfixes

Auch in Bezug auf das Testen hat das Vorteile. In einer OSGi-Umgebung kann ich genau nachvollziehen, wo mein Bundle X genutzt wird und sicherstellen, dass die betroffenen Stellen weiter funktionieren. Ungewünschte Seiteneffekte auf andere Funktionen sind durch die starke Kapselung in OSGi so gut wie ausgeschlossen. Vor allem in Bereichen, wo das Qualitätsmanagement und die Nachvollziehbarkeit einen großen Stellenwert hat, beispielsweise regulierte Bereiche wie die Medizinprodukteentwicklung, kann man durch OSGi Aufwände beim Testen reduzieren und die Qualität erhöhen, da man sich auf die geänderten Funktionen konzentrieren kann. Näheres zum Thema Testen mit OSGi wollen wir im nächsten Artikel dieser Reihe vorstellen.

Fazit

Wir haben einige Prinzipien von OSGi und deren Vorteile für die Anwendungsentwicklung erläutert. Aber wie sieht es aus mit der Nutzung von typischen Enterprise-Technologien wie JPA oder Web-Apps und Servlets? Die gute Nachricht ist, dass sich die meisten Java-EE-Technologien auch in einem OSGi-Umfeld nutzen lassen. Viele Java-EE-Application-Server basieren inzwischen unter der Haube auf OSGi, z. B. GlassFish, Wildfly, WebSphere oder Apache Geronimo. In der OSGi-Enterprise-Spezifikation [1] sind viele APIs für die Verwendung von typischen Enterprise-Technologien und -Services im OSGi-Umfeld beschrieben. Wie man z. B. JPA in einem Persistence Bundle in OSGi nutzt, hatten wir im letzten Teil dieser Artikelserie beschrieben (siehe Eclipse Magazin 4.2015). Neben verschiedenen Implementierungen dieser Spezifikationen unter anderem durch das Eclipse-Gemini-Projekt gibt es eine Reihe weiterführender Frameworks, die über die OSGi-Spezifkationen hinausgehende Enterprise-APIs für OSGi bereitstellen und so den Einstieg in die OSGi-Entwicklung erleichtern. Dazu gehören das OSGi-enRoute-Projekt der OSGi Alliance oder die Cloudyle-PaaS+-Enterprise-APIs, die Teil der Cloudyle-PaaS+-Plattform sind und OSGi- und Cloud-Technologien verbinden. Die Cloudyle-PaaS+-Plattform wendet sich speziell an OSGi-Entwickler und bietet vielfältige Unterstützung für den gesamten Application-Lifecycle. Das integrierte OSGi Bundle Repository erlaubt eine einfache Nutzung von vordefinierten oder eigenen Bundles.

Das PetClinic-Tutorial [12] führt durch die Erstellung einer Enterprise-Applikation mit OSGi und kann genutzt werden, um die in diesem Artikel beschriebenen Konzepte auszuprobieren.

Verwandte Themen:

Geschrieben von
Alexander Grzesik
Alexander Grzesik
Alexander Grzesik ist Entwicklungsleiter bei der Cloudyle GmbH und Softwarearchitekt der PaaS+ OSGi Cloud Platform. Als Experte im Bereich Java, PaaS und OSGi hält er auf Konferenzen regelmäßig Vorträge zu diesen Themen.
Kommentare

Hinterlasse einen Kommentar

1 Kommentar auf "OSGi für Enterprise-Applikationen"

avatar
400
  Subscribe  
Benachrichtige mich zu:
Harald Wellmann
Gast

WildFly basiert nicht auf OSGi. Die OSGi-Unterstützung aus JBoss AS 7 wurde in WildFly entfernt.

Auch JBoss AS basierte niemals auf OSGi, sondern unterstützte es nur, d.h. die Module des Servers waren selbst keine OSGi-Bundles, anders als bei GlassFish.