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.
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.
Listing 7: IDataProvider.java
/*** 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 <code>IKey</code>*/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).
Listing 8: ICachingStrategy.java
/*** 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).
Listing 9: DataproviderWrapper
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.















