Dependency Injection mit Eclipse
Glücklicherweise können wir im Client auch Dependency Injection nutzen. Wir machen uns hierzu die Tatsache zunutze, dass Eclipse für die Instantiierung von Extensions das Konzept der Extension Factory kennt. Hierzu müssen wir das Interface IExecutableExtensionFactory implementieren. Die Eclipse-API-Dokumentation äußert sich zum Interface IExecutableExtensionFactory folgendermaßen: "This interface allows extension providers to control how the instances provided to extension-points are being created by referring to the factory instead of referring to a class. [...] Effectively, factories give full control over the create executable extension process." Wir machen uns diese Möglichkeit zunutze, in dem wir eine IExecutableExtensionFactory implementieren, die die eigentliche Zielklasse (z.B. einen View oder eine Action) ganz normal instantiiert und anschließend Spring benutzt, um eventuell konfigurierte Dependencies zu injizieren. Da wir nicht die ersten sind, die dieses Problem haben, können wir auf eine von Martin Lippert und Heiko Seeberger implementierte ExecutableExtensionFactory zurückgreifen [3]. Folgende Schritte sind dazu notwendig:- Installieren der Extension Factory (Plug-in org.eclipse.springframework.util in den dropins-Ordner kopieren) und der Spring-Module (damit Spring in Form von OSGi-Modulen genutzt werden kann).
- org.eclipse.springframework.util als Dependency aufnehmen
- Definition einer Spring Bean für unseren View (oder Action etc.) – hier lassen sich beliebige Referenzen auf andere Spring Beans setzen, die später durch Spring aufgelöst werden:
<bean id="personView" class="de.eclipsemagazin.eercp.remoting.person.views.PersonView" scope="prototype"> <property name="personService" ref="personService" /> </bean>
- Anpassung der Extension, in der unser View angemeldet wird – hier wird nicht mehr die eigentliche Klasse des Views angegeben, sondern die ExecutableExtensionFactory sowie die ID der Spring Bean, die wir in Schritt 3 konfiguriert haben:
<view name="Spring Persons" icon="icons/sample.gif" category="de.eclipsemagazin.eercp.remoting.person" class="org.eclipse.springframework.util.SpringExtensionFactory:personView" id="de.eclipsemagazin.eercp.remoting.person.views.SpringPersonView"> </view>
- Beim Hochfahren des Plug-ins, das den Clientcode enthält, muss der Spring Context geladen und dann als OSGI-Service registriert werden – wichtig ist hier, dass der Kontext unter dem symbolischen Namen des Plug-ins registriert wird, da die Extension Factory den Kontext unter diesem Namen suchen wird:
ApplicationContext applicationContext = getContext();
Dictionary
Welche der beiden Ansätze (ClientServiceLocator vs. Dependency Injection) man wählt, ist wohl größtenteils Geschmackssache. Der ClientServiceLocator kann auf der Habenseite verbuchen, dass er sehr intuitiv einsetzbar ist. Allerdings wirkt der dadurch entstehende Code doch recht imperativ und bricht mit der durch Spring ins System gebrachten Inversion of Control. Die Nutzung von Dependency Injection, auch auf dem Client, wirkt hier natürlicher. Leider ist dazu derzeit (noch) ein etwas höherer Aufwand notwendig und auch die Konfiguration der Extensions in der plugin.xml wirkt etwas ungewohnt.
Alles eitel Sonnenschein – oder?
Auch wenn wir durch die Verwendung von Spring und clientseitige Dependency Injection das Programmiermodell auf dem Client sehr einfach halten konnten, handelt es sich bei der hier eingesetzten Technik immer noch um eine Remote-Kommunikation, die einige Herausforderungen mit sich bringt. Werfen wir also einen Blick auf die Problemstellen:- Call by Value (Objektidentität): Das Ergebnis eines entfernten Services stellt immer ein serialisiertes Objekt dar, der Methodenaufruf erfolgt also Call by Value. Eventuelle Caching-Mechanismen des O/R Mappers auf der Serverseite kommen hierbei nicht zum Tragen. Diese sorgen im Normalfall dafür, dass im Falle eines wiederholten Zugriffs ein Objekt aus dem Cache bedient wird, die Anfrage also mittels Call by Reference abgearbeitet wird. Manipulationen am Objekt sind damit transparent, sie werden also vom O/R Mapper automatisch persistiert. Da dieser Mechanismus auf dem Ergebnisobjekt einer entfernten Servicemethode nicht greift, müssen Aktualisierungen am Objekt explizit gesendet werden.
- Daten- und Transfervolumen: Für ein fachliches Objekt (z.B. eine Person) kann es mehrere technische Objekte geben, die in unterschiedlichen Service Calls zurückgeliefert werden. Die Menge dieser Objekte ist relativ schwer zu kontrollieren, da potenziell in jedem Service Call neue technische Repräsentationen der Fachlichkeit geladen werden. Abhilfe bietet hier zum einen ein verzögertes Laden bzw. ein clientseitiges Caching.
- Granularität der Zugriffe: Trotz der üblich sehr performanten Netzwerkanbindung, birgt der Aufruf eines entfernten Services immer einen gewissen Overhead. Die einzelnen Schritte der Kommunikation – Serialisieren, Verbindungsaufbau, Übertragung, Deserialisieren – sind, wenn auch transparent, ein Bestandteil sowohl des Requests als auch der Response. Es empfiehlt sich also, die Verwendung von entfernten Services bewusst in die Anwendung zu integrieren. Wichtig ist, die richtige Granularität der Services zu identifizieren. Feingranulare Zugriffe erzeugen sehr viel Kommunikations-Overhead, sinnvoll sind somit eher grobgranulare Zugriffe. Wenn auf dieser Ebene eventuell nicht mit dem Domänenmodell gearbeitet werden kann, sollte über die Verwendung von Data Transfer Objects (DTO) bzw. Value Objects (VO) nachgedacht werden, um die benötigte Datenstruktur zu erhalten.
- Lazy Loading (Nachladen des Objektbaums mit O/R Mappern): Das sehr komfortable automatische Nachladen, was zur Standardfunktionalität eines O/R Mappers gehört, kann clientseitig nicht verwendet werden. Hier ist es notwendig, situativ zwischen einer spezifischen Vorinitialisierung und dem Einsatz von DTOs bzw. VOs zu entscheiden.
- Fehlerbehandlung: Die Fehlerbehandlung besteht in verteilten Anwendungen potenziell aus zwei Komponenten, der Serverseite und der Clientseite. Zum einen ist es wichtig, wie Fehlersituationen übergreifend behandelt werden, zum anderen ist es von Bedeutung, dem Anwender eine adäquate Fehlermeldung zu präsentieren. Der Wunsch, diese Fehlermeldungen dann zentral zu verwalten und mehrsprachig zur Verfügung zu stellen, bildet eine zusätzliche Anforderung für die Fehlerbehandlung in verteilten Anwendungen mit Eclipse RCP.
Im Folgenden werden wir uns mit dem Thema Daten- und Transfervolumen beschäftigen. Die anderen Themen werden in der kommenden Ausgabe näher betrachtet.















