Suche
Remoting und Caching in verteilten Eclipse-RCP-Anwendungen

Enterprise Eclipse RCP – Teil 2: Remoting und Caching

Peter Friese, Stefan Reichert

Nachdem wir im ersten Teil von Enterprise Eclipse RCP einen Überblick über die Entwicklung von Enterprise-RCP-Applikationen gegeben haben, wollen wir uns in diesem Artikel mit dem Themenkomplex der Kommunikation zwischen Frontend und Backend beschäftigen. Wie bereits im Überblicksartikel dargelegt, gibt es verschiedene Varianten für die Architektur von verteilten Eclipse-RCP-Anwendungen: Client/Server, d.h. der Client greift direkt auf die Datenbank zu, und 3+ Tier, d.h. der Client kommuniziert mit einem Backend-System, das die Geschäftslogik enthält. In diesem Artikel wollen wir zeigen, wie eine Eclipse-RCP-Anwendung mit einer klassischen Drei-Schichten-Architektur umgesetzt werden kann. Dazu zeigen wir zunächst, wie die Kommunikation zwischen Frontend und Backend umgesetzt werden kann. Anschließend gehen wir auf mögliche Probleme ein und zeigen Lösungen hierfür auf.

Für die Kommunikation zwischen zwei verteilten Java-Anwendungen steht eine nahezu unüberschaubare Anzahl an Technologien zur Verfügung – angefangen bei Java RMI (Remote Method Invocation) über Web Services, Remote OSGi und CORBA bis hin zu eher proprietären Protokollen wie Hessian und Burlap. Der Einfachheit halber haben wir uns für Spring HTTPInvoker entschieden, da wir das Backend sowieso mit Spring realisieren wollen. Erfreulicherweise bietet Spring eine gute Abstraktionsschicht für Remoting-Protokolle, sodass ein späterer Wechsel des Transportprotokolls ohne große Änderungen möglich ist. Diese Abstraktionsschicht ist auch dafür verantwortlich, dass der entfernte Aufruf von Methoden völlig transparent und somit nicht von lokalen Aufrufen zu unterscheiden ist. Umständliche Lookups von Remote-Interfaces entfallen somit, was zu übersichtlicherem Quellcode führt. Um diese Illusion zu erzeugen, bedient sich Spring des Proxy-Patterns (siehe: Gamma, Helm, Johnson, Vlissides: “Design Patterns: Elements of Reusable Object-oriented Software”, Addison-Wesley [1]). Eine als Proxy agierende Klasse nimmt Aufrufe entgegen und leitet sie an das ursprünglich gemeinte Objekt weiter. In Java lassen sich Proxies mithilfe des Dynamic Proxy API (siehe: Stephan Anft, Peter Friese: “Der Prokurist – praktische Anwendungsszenarien des Dynamic Proxy API”, Java Magazin 10/2003. [2]) sehr elegant, weil generisch, implementieren.

Ein Beispiel

Zur Veranschaulichung soll ein einfacher Service zur Verwaltung von Personendaten dienen. Dieser Service stellt Methoden zum Anlegen und Modifizieren von Personendatensätzen bereit. Darüber hinaus implementiert der Service Methoden zum Laden von einzelnen bzw. einer Menge von Personendatensätzen (Listing 1). Die Kommunikation mit der Datenbank, in der diese Daten schlussendlich gespeichert werden, übernimmt eine DAO (DataAccessObject). Service und DAO werden von Spring per Dependency Injection verbunden (Listing 2). Bis hierhin entspricht dies exakt dem Vorgehen, das auch für die Implementierung einer Webapplikation angebracht wäre.

public interface IPersonService {
String sayHi(PersonDTO person);
PersonDTO loadPerson(java.lang.Long id);
List<PersonDTO> loadAll();
PersonDTO savePerson(PersonDTO person);
}
	
<?xml version="1.0" encoding="ISO-8859-1"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- Hibernate session factory -->
<bean class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" id="sessionFactory">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="configLocation"> <value>classpath:hibernate.cfg.xml</value>
</property>
</bean>
<bean class=" de.eclipsemagazin.eercp.repository.resource.PersonDAO" id="personDAO">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean class=" de.eclipsemagazin.eercp.service.resource.PersonService" id="personService">
<property name="personDAO" ref="personDAO"/>
</bean>
</beans>

Um den Service nun auch von extern (d.h. außerhalb der VM) ansprechbar zu machen, muss er exportiert werden. Dazu sind mehrere Schritte erforderlich. Zunächst muss der Service mithilfe des passenden Spring Service Exporters exportiert werden. In unserem Fall ist dies der HttpInvokerServiceExporter, der als Remote Proxy agiert. Die Verbindung zum eigentlichen Service wird über eine entprechende Dependency hergestellt (Listing 3). Alle Services einer Applikation, die von außerhalb angesprochen werden sollen, müssen analog in die Datei remoting-servlet.xmlaufgenommen werden. Alle anderen Services sind nicht öffentlich und können somit auch nicht von außen aufgerufen werden. Die exportierten Services bilden also den Remoting-Kontext. Der Name der Datei für diesen Remoting-Kontext richtet sich nach dem Namen des Servlets, das die Services exportiert. Spring stellt dazu die Klasse DispatcherServlet zur Verfügung, die wie gewohnt in der Datei web.xml konfiguriert wird (Listing 4).

<?xml version="1.0" encoding="ISO-8859-1"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!-- remoting exporters -->
<bean class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter" name="/PersonService">
<property name="service" ref="personService"/>
<property name="serviceInterface" value=" de.eclipsemagazin.eercp. service.resource.IPersonService"/>
</bean>
</beans>
	
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Dysis Backend</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
</web-app>
	 

[ header = Seite 2: Dependency Injection mit Eclipse ]

Auf Clientseite muss der Service nun importiert werden, hierzu stellt Spring die Klasse HttpInvokerProxyFactoryBean bereit, die als lokaler Proxy für den Service fungiert. Damit der Proxy den lokalen Aufrufern das entsprechende Interface vorgaukeln kann, muss er mit dem zu implementierenden Interface konfiguriert werden. Weiterhin muss die URL angegeben werden, unter der der entfernte Service tatsächlich erreichbar ist. In Listing 5 sieht man, wie durch geschickten Einsatz eines PropertyPlaceHolderConfigurers dafür gesorgt wird, dass die Vorgabewerte für die URL mithilfe von System-Properties überschrieben werden können. So wird sichergestellt, dass der Client nicht neu kompiliert werden muss, wenn der Server umzieht und unter einer anderen URL erreichbar sein sollte. Auch die Umschaltung zwischen verschiedenen Instanzen (z.B. Integration/Produktion) des Systems kann mit diesem Mechanismus realisiert werden.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="clientContextCustom.xml"/>
<bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="properties">
<props>
<prop key="remoteServer">localhost</prop>
<prop key="remotePort">8080</prop>
<prop key="remoteContext">dysis/remoting</prop>
</props>
</property>
<property name="systemPropertiesModeName"> <value>SYSTEM_PROPERTIES_MODE_OVERRIDE</value>
</property>
</bean>
<bean id="personService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://${remoteServer}:${remotePort}/${remoteContext}/PersonService" />
<property name="serviceInterface" value=" de.eclipsemagazin.eercp.service.resource.IPersonService" />
</bean>
</beans>

Will ein Client eine Methode auf einem Service im Server aufrufen, muss er zunächst Zugriff auf den Proxy erhalten. Nachdem er diese Referenz erhalten hat, kann der Aufruf der Methode wie gewohnt durchgeführt werden und ist, wie bereits geschildert, vollständig transparent:

public Object[] getElements(Object parent) {
List <PersonDTO> allPersons = getPersonService().loadAll();
return allPersons.toArray();
}

Die Referenz auf den Proxy kann der Client auf unterschiedliche Art und Weise erhalten: Bei einer Variante wird das ServiceLocator-Pattern eingesetzt, bei der anderen nutzen wir clientseitige Dependency Injection. Schauen wir uns zunächst den Service-Locator-Ansatz an. Der Service Locator kennt den clientseitige Spring-Kontext (üblicherweise wird der Name der XML-Datei die den clientseitigen Spring-Kontext definiert, als Konstante hinterlegt) und hat für jeden im Client-Kontext verfügbaren Service eine getter-Methode (Listing 6). Die Referenz auf den Service-Proxy kann dann durch einen einfachen Aufruf der entsprechenden getter-Methode erfragt werden:

personService = ClientServiceLocator.instance().getPersonService();

Im Sinne der besseren Übersichtlichkeit sollte der Zugriff auf diese Referenz mithilfe einer privaten getter-Methode im Aufrufer gekapselt werden:

private IPersonService getPersonService() {
if (personService == null) {
personService = ClientServiceLocator.instance().getPersonService();
}
return personService;
}
/**
* Locates and provides all available application services.
*/
public class ClientServiceLocator {
private final String DEFAULT_CONTEXT_LOCATION = "clientContext.xml";
private ClassPathXmlApplicationContext context = null;
private String contextLocation;
private ClientServiceLocator() {
// shouldn't be instantiated
}
private final static ClientServiceLocator instance = new ClientServiceLocator();
public static ClientServiceLocator instance() {
return instance;
}
public synchronized void init(String applicationContextLocation) {
contextLocation = applicationContextLocation;
context = null;
}
public synchronized ApplicationContext getContext() {
if (context == null) {
if (contextLocation == null) {
contextLocation = DEFAULT_CONTEXT_LOCATION;
}
Thread currentThread = Thread.currentThread();
ClassLoader originalClassloader = currentThread
.getContextClassLoader();
try { currentThread.setContextClassLoader(this.getClass()
.getClassLoader());
context = new ClassPathXmlApplicationContext(contextLocation);
} finally { currentThread.setContextClassLoader(originalClassloader);
}
}
return context;
}
public final IPersonService getPersonService() {
return (IPersonService) getContext().getBean("personService");
}
}

Wie man leicht sieht, ist der Zugriff auf entfernte Services mithilfe des Service Locator Patterns recht old-school und unnötig explizit. Neben der im Backend eingesetzten Sping-gesteuerten Dependency Injection sieht dieser Code regelrecht altbacken aus.

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 IExecutableExtensionFactoryfolgendermaß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 properties = new Hashtable();
properties.put("org.springframework.context.service.name", "de.eclipsemagazin.eercp.remoting.person");
context.registerService(ApplicationContext.class.getName(), applicationContext, properties); 

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.xmlwirkt etwas ungewohnt.

[ header = Seite 3: Alles eitel Sonnenschein – oder? ]

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.

Objektidentität und Datenvolumen

Wie bereits beschrieben, führt die Tatsache, dass beim Aufruf eines entfernten Service das Caching des O/R Mappers nicht greift, zu technischen Dubletten eines fachlichen Objekts. Da diese Dubletten potenziell bei jedem Aufruf entstehen, sollte hierauf in bestimmten Fällen besonderes Augenmerk gelegt werden. Berechtigterweise kann man sagen, dass beim Laden von Einzelsatzdaten der Effekt zu vernachlässigen ist. Einzelsatzdaten sind oft mit einem Editor verbunden und werden nach dem Schließen des Editors von der Garbage Collection entfernt. Interessant wird es jedoch beim Laden von konkreten Objektlisten. Eclipse RCP als Plattform bietet viele Möglichkeiten, die Eingabe von Daten zu vereinfachen. Autocompletion und Adhoc-Validierung bieten dem Anwender einen sehr guten Bedienkomfort. Da es häufig nicht empfehlenswert ist, für jede Autovervollständigung bzw. Validierungsaufruf die Runde über den Server zu drehen, erfordern beide Funktionen zumeist eine entsprechende Menge an Daten. Die Autovervollständigung muss zumindest eine Teilmenge der potenziell gültigen Eingaben kennen, um einen gültigen Wert vorschlagen zu können – eine Validierung sollte dagegen, wenn möglich, die gesamte Menge kennen. Um nun zu vermeiden, dass diese Daten weder in dem einen noch in dem anderen Fall mehrfach geladen werden müssen, ist es nur sinnvoll, diese Daten an zentraler Stelle im Client vorzuhalten, in anderen Worten, clientseitig zu cachen.

Clientseitiger Cache

Zugegebenermaßen muss die Entscheidung, Daten clientseitig zu cachen, situativ getroffen werden. In jedem Fall muss der erhöhte Zeitbedarf für den Aufruf einer entfernten Servicemethode gegen den erhöhten Speicherbedarf im Client gegenübergestellt und bewertet werden. Die Implementierung des Cache ist also im Idealfall transparent und bei Bedarf aktivierbar.

Wir stellen als Lösung ein Konzept für eine clientseitige Datenzugriffsschicht vor, das genau diese Funktionalität bietet. Diese clientseitigen DAOs, die so genannten Dataprovider arbeiten grundsätzlich ähnlich wie der Cache des O/R Mappers. Sie stellen eine generische Schicht im Client dar, die sowohl Zugriff auf Einzelsatzdaten als auch Objektlisten bietet. Durch den generischen Ansatz ist es möglich, dynamisch eine Cache-Implementierung transparent einzuhängen.

[ header = Seite 4: Dataprovider ]

Dataprovider

Zentrales Objekt der Zugriffschicht ist das IDataProvider-Interface. Listing 7 zeigt, dass es die Standardzugriffsmethoden auf Daten bereitstellt:

getData(IKey): Diese Methode wird zum Laden von Einzelsatzdaten verwendet. Als Identifikation dient hier ein Objekt, das das Markerinterface IKey implementiert. Dieses Objekt dient als Schlüssel, unter dem wieder auf den Einzeldatensatz zugegriffen werden kann.

getCollectionData(ICollectionKey): Um auf eine Liste von Daten zuzugreifen, wird ebenfalls ein Markerinterface ICollectionKey verwendet. Dieses Objekt dient ebenfalls als Schlüssel, unter dem wieder auf die Collection zugegriffen werden kann.

/**
* Interface for a provider of a specific kind of data.
*/
public interface IDataProvider {
/**
* @return the type of this <code>IDataProvider</code>
*/
String getType();
/**
* @return the group this <code>IDataProvider</code> belongs to.
*/
String getProviderGroup();
/**
* @return the data with the given IKey
*/
Object getData(IKey key);
/**
* @return the <code>Collection</code> of data with the given
* <code>ICollectionKey</code>
*/
@SuppressWarnings("unchecked")
Collection getDataCollection(ICollectionKey collectionKey);
/**
* @return the <code>IKey</code> for a given piece of data
*/
IKey getKey(Object data);
}
	

Um das Handling der Dataprovider so flexibel wie möglich zu halten, werden diese über eine Extension registriert. Über den von der Methode getType() jeweils zurückgegebenen Typ kann die konkrete registrierte Implementierung über der Registry angefragt werden.

Transparentes Caching

Der zentrale Zugriff auf einen Dataprovider über die Registry bietet die Möglichkeit, ein Caching einzuhängen. Ist in der Extension für den Dataprovider eine Caching Strategy hinterlegt, reagiert die Registry entsprechend und umhüllt den geladenen Dataprovider mit einem Wrapper. Durch die generische Zugriffsmethode mit den Markerinterfaces IKey und ICollectionKey, kann dieser Wrapper vorher an eine entsprechende Caching-Strategie delegieren (Listing 8).

/**
* Interface for a caching strategy.
*/
public interface ICachingStrategy {
/**
* Evaluates whether the data with the given <code>IKey</code> expired
* yet. This method should ensure, that the given entry exists by calling
* <code>exists(IKey)</code> beore evaluating the given
* <code>ICacheEntryInformation</code>.
*
* @param key
* The <code>IKey</code> of the investigated data
* @param cacheEntryInformation
* The <code>ICacheEntryInformation</code> of the investigated
* data
* @return whether the data has expired
*/
boolean expired(IKey key, ICacheEntryInformation cacheEntryInformation);
/**
* Evaluates whether the data with the given <code>IKey</code> exists. If
* <code>evictCachedData(IKey)</code> was called for this <code>Key</code>
* before, this method should return <code>false</code>.
*
* @param key
* The <code>IKey</code> of the investigated data
* @see #expired(IKey, ICacheEntryInformation)
*/
boolean exists(IKey key);
/**
* @return the data with the given <code>IKey</code>
*/
Object getCachedData(IKey key);
/**
* Evict the entry for the data with the given <code>IKey</code>.
*
* @param data
* The updated data
*/
void evictCachedData(IKey key);
/**
* Registers an entry for the data with the given <code>IKey</code>.
*
* @param key
* The <code>IKey</code> of the data to register
* @param data
* The data to rigister for the given <code>IKey</code>
* @param cacheEntryInformation
* The corresponding <code>ICacheEntryInformation</code>
*/
void registerCachedData(IKey key, Object data,
ICacheEntryInformation cacheEntryInformation);
/**
* Clears the whole cache.
*/
void clearCache();
}

Die Caching-Strategie sorgt für das eigentliche Caching. Da auch diese über ein Interface ICachingStrategy definiert wird, kann hier entweder auf eine einfache Map-basierte Implementierung zurückgegriffen, bei Bedarf aber auch eine spezifischere Lösung transparent eingebunden werden. Der Wrapper für den Dataprovider prüft beim Aufruf der getData(IKey)-Methode den Zustand des Caches und reagiert entsprechend (Listing 9).

public Object getData(IKey key) {
CacheEntryInformation cacheEntryInformation = cacheInformationMap
.get(key);
if (cacheEntryInformation != null
&& !cachingStrategy.expired(key, cacheEntryInformation)) {
cacheEntryInformation.registerAccess();
return (Object) cachingStrategy.getCachedData(key);
}
else {
Object data = adaptedDataProvider.getData(key);
flushCacheForUpdate(key, data);
return (Object) data;
}
}
	

Da der Wrapper selbst das IDataProvider-Interface implementiert, ist das Caching für den aufrufenden Codeteil transparent. Dieser arbeitet auf den vom Interface angebotenen Methoden. Die Verwendung von Extensions macht die Konfiguration zusätzlich flexibel. Um das Caching zu aktivieren, muss lediglich die Extension um die Definition einer ICachingStrategy erweitert werden.

Caching auf der Clientseite stellt wie gesagt nur einen Lösungsansatz für einen Teil der Herausforderungen einer auf Remoting basierenden Architektur dar. Im nächsten Teil von Eclipse Enterprise RCP werden wir näher auf die Themen Security und Fehlerbehandlung eingehen.

Geschrieben von
Peter Friese
Peter Friese
Peter Friese arbeitet als Developer Advocate bei Google im Developer Relations Team in London. Er trägt regelmäßig zu Open-Source-Projekten bei, spricht auf Konferenzen und bloggt unter http://www.peterfriese.de. Auf Twitter findet man ihn unter @peterfriese und auf Google+ unter +PeterFriese.
Stefan Reichert
Stefan Reichert
Stefan Reichert, Diplom-Wirtschaftsinformatiker (FH), arbeitet als Software Engineer bei Zühlke Engineering in Hamburg. Er beschäftigt sich seit mehreren Jahren mit verteilten Java-Enterprise-Anwendungen, serviceorientierten Architekturen, Eclipse und Eclipse RCP. Seit einiger Zeit begeistert ihn die Entwicklung von Apps mit Android. Darüber hinaus ist er Buchautor und verfasst regelmäßig Fachartikel. Kontakt: stefan[at]wickedshell.net, http://www.wickedshell.net/blog
Kommentare

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *