Von Veronika Schäfers auf Montag, 09. April 2018
Kategorie: Application Development

Service Provider & Consumer - lose gekoppelt ein starkes Team

​Dass spezifische Funktionalitäten in unterschiedliche Services gebündelt sind, ist ja nichts Neues. Und dass die Funktionalitäten eines Services oft in einem Interface definiert und dann in der einer anderen Klasse implementiert werden, auch nicht. Doch mit dem in Java 9 dazugekommenem Modul-System können nun auch einzelne Services modularisiert werden und das bringt viele Vorteile mit sich!

Bevor wir zu den Vorzügen dieser Modularisierung kommen, möchte ich zunächst einmal zeigen, wie einfach diese umzusetzen ist. Hierzu ein kleines Beispiel, welches das Prinzip veranschaulichen soll: Zur Begrüßung und Verabschiedung soll eine nette Nachricht geliefert werden. 

Das Interface sieht in diesem Fall wie folgt aus:

​Das Interface besitzt nur zwei Methoden, welche die entsprechenden Nachrichten zurückgeben. Damit dieser Service auch für andere Module verfügbar ist, muss dieser exportiert werden. Ich habe hierfür im Modul-Deskriptor einfach das jeweilige Package nach außen hin verfügbar gemacht. Man hätte aber auch nur das Interface selbst exportieren können.

​Unser vorhin definierter Service muss nun noch implementiert werden. Zum Beispiel kann dieser Nachrichten in verschiedenen Sprachen oder spezifisch für einzelne Personen(gruppen) zurückgeben. Ich habe nun in einem anderen Projekt einen Service geschrieben, der für die Begrüßung/Verabschiedung in deutscher Sprache verantwortlich ist.

​Um das Interface implementieren zu können, muss das Modul des Service Providers den Service kennen (Schlüsselwort requires).
Wenn die Umsetzung des Services in einem anderen Modul verwendet werden können soll, dann muss dieser für die Außenwelt bereitgestellt werden. In unserem Beispiel bedeutet dies konkret, dass der Modul Provider den Service ​​​​​​HelloAndBye mit der Implementierung der Klasse ​​GermanHelloAndByeService zur Verfügung stellt (provides <service> with <implementation>
).

Nun kann der Service auch in anderen Modulen genutzt werden. Ich habe dafür wieder ein eigenes Modul definiert, welches diesen Service nutzt (Stichwort uses).

​Der Consumer kennt also bisher nur das Interface und muss die Verknüpfung zum Provider noch auflösen. Diese Aufgabe übernimmt für uns die Klasse ServiceLoader, welche bereits seit Java 6 vorhanden ist und das Laden von Services ermöglicht. Erst zur Laufzeit findet der ServiceLoader heraus, welche Implementierung genutzt werden soll. Hierfür muss sich diese dann auch im Klassen- oder Modulpfad befinden.

​In unserem Beispiel findet der ServiceLoader zur Laufzeit genau die eine Implementierung des angegebenen Services ​HelloAndBye. Die Methoden des Services können dann angesprochen werden und liefern das gewünschte Ergebnis:

​Funktionieren tut das Ganze also schon Mal. Aber was bringt es uns?
Das Praktische ist, dass der Consumer zur Compile-Zeit den Provider nicht kennt und völlig unabhängig von ihm entwickelt werden muss. Auch der Provider weiß nichts von dem Consumer. Es bestehen bis auf das definierte Service Interface keine Abhängigkeiten zueinander. Der Provider kann so sehr einfach wiederverwendet werden.
Neue Service-Implementierungen können außerdem zu jeder Zeit erweitert, nachträglich hinzugefügt der komplett ausgetauscht werden. Dafür muss beim Consumer keine einzige Zeile Programmcode geändert werden! Der entsprechende Service Provider muss nur im Modul-/Klassenpfad angepasst werden.

Insgesamt ziemlich simpel und passt hervorragend zum Modulsystem von Java 9. Mich hat diese Neuheit überzeugt!

Kommentare hinterlassen