Strom allein ist nicht genug

Zukunftssicher unterwegs mit OSGi und REST

Wolfgang Werner

Die Entwicklung von Software für die Steuerung von Ladestationen für Elektrofahrzeuge stellt Java-Softwarearchitekturen vor viele Herausforderungen. Hohe Verfügbarkeitsanforderungen erschweren Updates der laufenden Serversoftware, Betreiber haben keinen Einfluss auf Aktualität und wenig Einfluss auf die Technologie der zugreifenden Clients. Während der Einführungsphase sind die Weiterentwicklung der Software und kurze Reaktionszeiten aber ein Muss, um sich in einem volatilen Markt- und Technologieumfeld behaupten zu können. Außerdem soll das finale Produkt interagierende Services und Komponenten von verschiedenen Parteien enthalten: vom Hersteller über den Betreiber der Ladestation bis hin zum Fahrzeughersteller oder zum Anbieter von Flottenlösungen für Elektromobilität. Anhand dieses fiktiven Beispiels wird im Folgenden eine Architektur basierend auf OSGi Declarative Services und REST abgeleitet, die diese Herausforderungen meistern kann. Die verwendeten Technologien und Konzepte werden vorgestellt und anhand einer prototypischen Implementierung illustriert.

Folgende nicht funktionale Anforderungen zeichnen das eingangs beschriebene Szenario aus: Die modulare Komposition unterschiedlicher Services von verschiedenen Anbietern muss sowohl während der Entwicklung als auch zur Laufzeit des Systems möglich sein; einzelne Komponenten des Systems sollen zur Laufzeit aktualisierbar sein. Dabei muss gewährleistet sein, dass Änderungen entweder rückwärtskompatibel sind oder inkompatible Versionen parallel betrieben werden können. Der Grad der Kompatibilität muss für alle Parteien eindeutig ersichtlich sein und maschinell ausgewertet werden können. Weiterhin soll das Serversystem so wenige Annahmen über die Technologie von Clientzugriffen wie möglich treffen, um zukünftigen Entwicklungen nicht im Weg zu stehen.

OSGi Declarative Services

Im OSGi Framework steht ein Modell für die Implementierung von Services auf Basis von POJOs und eine zentrale Registry für die Services zur Verfügung. Dadurch können Objekte über die Grenzen einzelner Bundles hinweg verwendet werden. Services können sowohl programmatisch als auch deklarativ beschrieben werden. Die programmatische Verwendung von OSGi Services ist allerdings komplex, wenn die Dynamik von OSGi bewahrt werden soll. Keine Komponente im System kann sich darauf verlassen, dass zum Zeitpunkt der Ausführung alle benötigten Services zur Verfügung stehen. Deshalb muss die An- und Abmeldung jedes Services aktiv getrackt und behandelt werden. OSGi Declarative Services (OSGi DS) hebt die Registrierung und Verwendung von Services auf eine deklarative Ebene: Service Components werden in XML beschrieben, die Auflösung der Abhängigkeiten, Aktivierung und Deaktivierung wird von der so genannten Service Component Runtime (SCR) übernommen. Die verschiedenen Parteien unseres Szenarios können also die implementierten Komponenten als Service Components zur Verfügung stellen. In der Laufzeitumgebung werden sie dann zusammengefügt. Das Quell-Bundle eines Service Components ist dabei irrelevant. So können Applikationen aus von verschiedenen Parteien entwickelten Teilen zusammengefügt werden, ohne dass der Code dieser Parteien, mit Ausnahme gemeinsam genutzter APIs, den jeweils anderen bekannt sein muss. Für den Proof of Concept verwenden wir restlet-osgi von Bryan Hunt [4] zur Integration von Restlet mit OSGi DS. Für die produktive Implementierung würde man in der Rolle des Herstellers eher direkt auf OSGi DS aufsetzen und den anderen Parteien, die Services anbieten, ein weiter abstrahiertes API zur Verfügung stellen.

REST und HATEOAS

Für die Kommunikation zwischen Client und Server wählen wir ein RESTful API über HTTP, um einerseits eine weitgehende Entkopplung zwischen den kommunizierenden Parteien zu erreichen und andererseits eine hohe Variabilität der zu übertragender Repräsentationen anbieten zu können. Folgende Grundsätze von REST haben einen direkten Einfluss auf unseren Anwendungsfall: statuslose Kommunikation, die Verwendung von Hypertext, die Bereitstellung mehrerer Repräsentationen für Ressourcen und das gemeinsame Interface aller Komponenten. Hält der Server den Status von Clientsessions im Speicher, so sind während der Laufzeit einer Session Updates nur dann möglich, wenn die Session persistiert wird und sich die in der Session verwendeten Datenstrukturen nicht durch das Update ändern. Deutlich sicherer ist in diesem Fall, eine Zeit ohne Traffic für Deployments zu wählen. Hätte eine Session in unserem Beispiel die Dauer eines Ladevorgangs, wären mögliche Updatezeiten sehr begrenzt. Bei statusloser Kommunikation kann ein Update grundsätzlich zwischen zwei Abfragen einer logischen Session durchgeführt werden.

REST fordert die Verwendung von Hypertext als Repräsentation der Serverressourcen, sodass es nicht notwendig ist, dem Client deren Struktur bekannt zu machen. Dieser muss nur noch wissen, welche Aufgaben er durchführen will, was die Weiterentwicklung des Servers deutlich vereinfacht. Der Server leitet den Client über Hyperlinks zur nächsten benötigten Ressource um, ganz wie wir es als Nutzer des WWW gewohnt sind. Auch wenn es den Benutzern nicht bewusst ist, beschreiben die Schritte, die sie beim Surfen im Netz ausführen, ein Kommunikationsprotokoll, beispielsweise zum Kaufen eines Buchs oder zur Anlage eines Blog-Posts [1]. Bei der Übertragung dieses Konzepts auf die Kommunikation von Maschine zu Maschine wird oft von HATEOAS gesprochen: „Hypermedia as the Engine of Application State“.

Die Bereitstellung von mehr als einer Repräsentation für Ressourcen bringt noch mehr Flexibilität für die Unterstützung verschiedenster Clients. Der Server kann identische Informationen beispielsweise als XML, JSON oder auch als proprietäres Binärformat anbieten. Client und Server können die am besten geeignete Repräsentation automatisiert untereinander aushandeln [2].

Die Nutzung eines gemeinsamen Interface (Uniform Interface) für alle Komponenten eines Systems ist eine weitere zentrale Forderung [3] des REST-Architekturstils. Dadurch wird es möglich, die Komponenten eines Systems frei miteinander zu verknüpfen. Im WWW nutzen wir dieses Konzept täglich: Wenn Sie eine Website aufrufen, greifen Sie meist nicht direkt auf den hostenden Server zu. Es ist für Ihren Browser aber transparent, ob und welche Proxies, Caches und virtuellen Hosts zwischen dem Konsumenten und dem Anbieter liegen. Während diese Komponenten infrastruktureller Natur sind, werden die tatsächlichen Nutzdaten als Ressourcen dargestellt. Diese sind die Ziele von Hypertextverweisen/URIs.

Restlet

Restlet ist eine Bibliothek, die die Entwicklung von Anwendungen im REST-Architekturstil in Java so einfach wie möglich machen soll. Eine typische Restlet-Server-Applikation besteht aus einer Applikationsklasse, einem oder mehreren Routern und einer Reihe von Ressourcenklassen. Zusätzlich können an verschiedenen Stellen in der Applikation Filter zwischengeschaltet sein, um beispielsweise Zugriffsrechte zu prüfen (org.restlet.security.Guard) oder Zugriffe zu protokollieren (org.restlet.engine.log.LogFilter). Applikationen, Router und Filter implementieren das Interface Uniform (org.restlet.Uniform) , das nur eine Methode für die Abarbeitung eines Requests definiert, und erweitern die abstrakte Klasse Restlet (org.restlet.Restlet) .

Neben diesen Klassen, die in der beschriebenen Implementierung direkt verwendet werden, bietet Restlet viele weitere Komponenten von Ressourcen für den Dateisystemzugriff über virtuelle Hosts, Server und Clients bis hin zu Erweiterungen, die die Integration mit anderen Frameworks möglich machen. Auch wenn es das API nicht direkt widerspiegelt, ist es sinnvoll, Restlet gedanklich als Composite Pattern zu behandeln: Jede Komponente ist ein Restlet (Component); es gibt spezielle Restlets, die weitere Restlets enthalten können (Composites). Ressourcen sind die Leaf-Components in der Laufzeitstruktur (Abb. 1).

Abb. 1: Klassendiagramm der verwendeten Restlet-Infrastrukturkomponenten

Die freie Komponierbarkeit ist für unseren Anwendungsfall relevant: Verschiedene Parteien sollen interagierende Komponenten zur Verfügung stellen können. So kann beispielsweise ein Anbieter einer E-Car-Sharing-Flotte eine Anwendung bereitstellen, um Fahrzeuge an der Station ein- und auszubuchen und die Standorte der nächsten freien Fahrzeuge anzuzeigen. Der Betreiber der Ladestation kann die Zugriffe auf diese Applikation weiterhin filtern, um Berechtigungsprüfungen durchzuführen oder die Einhaltung bestimmter Zugriffs-/Ressourcen-Quotas sicherzustellen. Parallel zu dieser Anwendung können noch weitere Anwendungen zur gleichen Zeit in derselben Runtime laufen (Abb. 2).

Abb. 2: Laufzeitstruktur einer Restlet-Applikation
Geschrieben von
Wolfgang Werner
Kommentare

Schreibe einen Kommentar

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