Blog Wieviel Service Dynamik soll es denn sein?

Wieviel Service Dynamik soll es denn sein?

Service Dynamik in OSGi handhaben

F√ľhrt man sich die einschl√§gige OSGi-Literatur zu Gem√ľte, gelangt man schnell zur Thematik Service Dynamik. In diesem Kontext werden meist verschiedene Optionen pr√§sentiert, wie ein brauchbarer Zustand von Service-Referenzen aus Client-Sicht sichergestellt wird. In den meisten F√§llen ist ein ServiceTracker eine gute L√∂sung zur Behandlung der Service Dynamik. Mit dem folgenden Code Snippet kann z.B. auf die Verf√ľgbarkeit eines Services gewartet werden:

try {

     Filter filter = bundleContext.createFilter("(" + "objectClass" + "=" + serviceClass.getName() + ")");   

     ServiceTracker tracker = new ServiceTracker(bundleContext, filter, null);

     tracker.open();

     T service = tracker.waitForService(timeout);

     if (service == null) {

             throw new ServiceNotAvailableException();

     }

     return service;

} catch (InvalidSyntaxException e) {

     throw new RuntimeException("Invalid Syntax for Filter " + serviceFilter + ".");

} finally {

     if (tracker != null) {

             tracker.close();

     }

}

Bei intensiver Verwendung von Services, k√∂nnen solche Tests beim Coden wirklich l√§stig werden. Gl√ľcklicherweise gibt es die deklarativen Ans√§tze zur Bindung von Services, die hier Abhilfe leisten (Declarative Services und Blueprint).

Um zu entscheiden, wie die Service Dynamik behandelt wird, muss aus meiner Sicht auch die Frage beantwortet werden: ‚ÄěIst der verwendete Service optional oder ist er basaler Kernbestandteil meiner Anwendung?‚Äú Im ersten Fall ist die Behandlung in der Regel aufwendiger. Im zweiten Fall ist die Anwendung bei nicht Verf√ľgbarkeit des betreffenden Services defekt und die Dynamik muss ggf. nicht aufwendig behandelt werden. Dieser Standpunkt wird im Folgenden erl√§utert.

Lose Kopplung von Modulen √ľber Services

Die Interaktion zwischen Modulen (Bundles) in einem OSGi-Framework wird idealerweise √ľber Services organisiert. Dadurch beschr√§nken sich Abh√§ngigkeiten zwischen Bundles auf Definitionen von Schnittstellen in Form von Java-Interfaces. Deren Implementierungen sind vollst√§ndig entkoppelt und werden erst zur Laufzeit als Service bereitgestellt. Ein Client eines Services kann unter dem Interface-Namen einen Service aus der OSGi-Service-Registry beziehen.

Anwendern von IoC-Containern (Inversion of Control resp. Dependency Injection) ist ein solches Vorgehen vertraut. Innerhalb einer Klasse wird die Abh√§ngigkeit zu einer Komponente √ľber ein Interface referenziert und zur Laufzeit wird die Bindung an eine Implementierung per Injektion hergestellt.

Service Dynamik mit der Low Level API handhaben

In unserem ersten mit OSGi realisierten Projekt haben wir die Low Level API f√ľr einen Lookup-Service¬† verwendet. Services werden also nicht √ľber einen deklarativen Ansatz in die Clients injeziert sondern per Aufruf der Service Registry geholt. Dies hat nicht nur den unsch√∂nen Nebeneffekt, dass eine Abh√§ngigkeit zur OSGi-API im Code existiert und daher f√ľr Business-Klassen Proxies gebaut werden sollten, die die OSGi-API bedienen. Vielmehr muss mit der Tatsache umgegangen werden, dass Services in einer OSGi-Laufzeitumgebung dynamisch sind, d.h. der Client kann nicht mit einer garantierten Verf√ľgbarkeit rechnen.

Um diesen Umstand in den Griff zu bekommen ist einiges an Aufwand zu betreiben. In den meisten F√§llen ist hierf√ľr ein ServiceTracker (s.o.) und ggf. ein dazu passender ServiceTrackerCustomzier geeignet. Mittels des ServiceTracker kann √ľber die Call-Back-Methoden des ServiceTrackerCustomizers auf √Ąnderungen bzgl. des Service-Status reagiert werden.

Aufgrund dieses Aufwands und der damit verbundenen Frage ‚ÄěWas soll passieren, wenn der Service mal nicht da ist?‚Äú, stellt sich die Frage, in welchen Situationen sinnvoll auf das Nichtvorhandensein eines Services reagiert werden kann.

Wie dynamisch darf ein Service sein?

Bei n-design entwickeln wir f√ľr unsere Kunden Unternehmenssoftware. Solche Anwendungen werden schnell komplex und daher von uns in der Regel in Module gegliedert, deren Implementierungen √ľber Services abstrahiert werden. In der Folge haben wir es mit einer gro√üen Anzahl von Services zu tun. Die allermeisten davon sind aber ausgenommen von der Start-p Phase nicht dynamisch sondern zwingend f√ľr die Ausf√ľhrung der Applikation erforderlich. Sie werden daher zur Laufzeit nicht beendet oder ausgetauscht.

Die meisten als Service registrierten Instanzen besitzen in unseren Anwendungen nicht einen typischen Service-Character im Sinne einer Schnittstelle zu einem externen Modul der Anwendung. D.h. der Gro√üteil der Services entspricht dem Service-Begriff, wie er in der Domain-Driven Design Community verwendet wird. Beispielsweise wird bei uns jedes Data Access Object (DAO) als Service bereitgestellt. Diese Services sind innerhalb ihres Moduls zwingend erforderliche Bestandteile, so dass ihre Nicht-Verf√ľgbarkeit zum kompletten Ausfall des betreffenden Moduls f√ľhrt.

Beim Design einer Anwendung, die in OSGi realisiert wird, muss also ber√ľcksichtigt werden, welche Module bzw. Services optional sind. Dies k√∂nnen z.B. Plug-In-artige Bestandteile sein, wie etwas die Apps in unserer Software n-pat. Man kann dann auf einer grob-granularen Ebene in der Anwendung auf die Servicedynamik reagieren, in dem z.B. ein Plug-In abgeschaltet wird, wenn einer seiner nicht optionalen Services nicht mehr verf√ľgbar ist.

Declarative Services und Dependency Injection

Die meisten der Services werden in unseren Anwendungen also als vorhanden vorausgesetzt. Gl√ľcklicherweise kann der Aufwand zur Pr√ľfung der Verf√ľgbarkeit von Services deutlich reduziert werden, in dem die deklarativen M√∂glichkeiten zur Service-Bindung von OSGi verwendet werden. Daf√ľr stehen Declartive Services oder Blueprint zur Verf√ľgung.

Bei n-design bevorzugen wir Blueprint. Blueprint ist aus dem Spring-Kontext entstanden und entspricht in seiner Verwendung im Wesentlichen den Mustern, die vom SpringContext bekannt sind. Zus√§tzlich k√∂nnen hier noch OSGi-Services registriert und referenziert werden, so dass diese als Dependencies einem Bean injeziert werden k√∂nnen. Dabei wartet Blueprint mit der Bereitstellung eines Services oder Beans, bis alles referenzierten Services verf√ľgbar sind.

Ein weiterer gro√üer Vorteil ist, dass Blueprint nicht den Service direkt sondern einen dynamisch generierten Proxy injeziert. Das macht die Clients eines Services robust gegen einen Austausch des Services zur Laufzeit. Sollte zur Laufzeit ein Service l√§ngere Zeit nicht mehr verf√ľgbar sein, quittiert Blueprint dies mit einer entsprechenden Exception. Dies macht die Behandlung der Service Dynamik sehr einfach.

Blueprint besticht durch seine einfache Verwendung. Declarative Services (DS) bieten aber mehr Kontrolle √ľber die Art der Abh√§ngigkeit von Services untereinander. Wird z.B. eine Service-Referenz A innerhalb eines Services B als static (muss verf√ľgbar sein) deklariert, dann wird bei deregistrieren des Services A zuerst der Services B deregistriert. So kann kontrolliert eine Kette von Services deaktiviert werden.

Fazit

F√ľr uns ist die Verwendung von Blueprint eine enorme Erleichterung, um die Service Dynamik in unseren Produkten zu behandeln. Fehlen nicht optionale Kern-Services, k√∂nnen wir durch entsprechendes ExceptionHandling die Anwendung oder ein Plug-in z.B. kontrolliert beenden. Wir beantworten f√ľr jeden einzelnen Service die Frage, ob dieser optional oder obligatorisch ist. Nur im Falle eines optionalen Services ist ggf. eine aufwendigere Behandlung der Service Dynamik notwendig z.B. durch Ausblenden von entsprechenden GUI-Elementen.

 

Kommentar schreiben

Sicherheitscode
Aktualisieren