Es ist serviert: Eine Anwendung mit EMF und CDO

Modeling goes Enterprise II

Im ersten Teil von „Modeling goes Enterprise“ wurden die grundlegenden Konzepte und Mechanismen beleuchtet, die Eclipse Modeling zum Einsatz in großem Maßstab verhelfen. Da Modeling teilweise immer noch als reine Aktivität zur Verkürzung der Entwicklungszeit angesehen wird, zeigt dieser zweite Teil an einem wirklichkeitsnahen Beispiel, wie Modeling-Technologien auch während der Laufzeit eines verteilten Softwaresystems sehr effizient eingesetzt werden können. Das elektronische Restaurant eDine demonstriert die einfache Umsetzung von Anforderungen an Skalierbarkeit, Verteilung, Persistenz, Sicherheit und Transaktionalität. Bei soviel Fokus auf die Fachlichkeit sind baldige Gaumenfreuden zu erwarten.

Das Restaurant eDine präsentiert sich mit einem ganz neuen Konzept. Alle Tische sind mit Touchcomputern für die Speisenauswahl, die Bestellung und die Zahlungsabwicklung ausgestattet. Zur Optimierung der internen Abläufe zwischen Tisch, Theke, Küche und Lager sind auch dort entsprechende Syst

emkomponenten installiert, und schließlich können die Kunden des angeschlossenen Bringdienstes ihre Bestellungen mit dem heimischen Webbrowser erledigen.

Das gesamte Softwaresystem basiert auf Eclipse und wurde, insbesondere Dank dem konsequenten Einsatz von Eclipse Modeling Tools und Techn

ologien, in nur wenigen Tagen entwickelt.

Tag 1: Die Architektur

Nach einer ausführlichen Analyserunde fällt die Entscheidung für die Entwicklung des folgenden Client-/Serversystems: Der zentrale Server wird mit Eclipse/OSGi, dem Eclipse Modeling Framework, dem CDO Model Repository und der Net4j Signalling Platform für die Netzwerkanbindung der verschiedenen Clients bestückt. Die für das elektronische Restaurant relevanten Businesskonzepte werden mit Ecore, dem omnipotenten Metamodell von EMF, modelliert und mit dem EMF Generator in Java-Code überführt. Auf diesem Modellcode bauen dann spezifische Eclipse-RCP-Clients auf, die die Geschäftsprozesse an den Tischen, der Theke und in der Küche unterstützen und dabei auf einem gemeinsamen, verteilten Geschäftsmodell operieren. Auf demselben Modell operiert auch eine Servlet-basierte Webanwendung, die dem Heimbesteller die aktuelle Bestellkarte im Webbrowser präsentiert. Die Kollaboration der verschiedenen Anwendungen geschieht dabei intern ausschließlich über das Committen von Modelltransaktionen auf das CDO Repository sowie über die Änderungsnachrichten, die von CDO automatisch verbreitet und von den Anwendungen mit normalen EMF-Adaptern abgegriffen werden. Am Ende des Tages wird ein Deployment-Diagramm erstellt (Abb. 1), das zeigt, wie die verschiedenen Systemkomponenten zusammenarbeiten.

Abb. 1: Deployment-Diagramm

Tag 2: Der Repository-Server

Der Aufbau des zentralen Servers ist weitgehend unabhängig von den fachlichen Anforderungen, daher wird er zuerst entwickelt. Dazu wird im Workspace das Plug-in-Projekt org.gastro.server erstellt, das lediglich einen Bundle Activator und die OSGi-Applikation enthält, über die das Repository von außen gestartet und gestoppt werden kann. Beide Klassen bauen auf Net4j Utilities auf und unterscheiden sich daher ein wenig von den üblichen Implementierungen. Durch die Verwendung des Net4j-Operations-und-Maintenance-Frameworks (OM) können wesentliche Plattformdienste wie Logging, Tracing, Preferences und Progress Monitoring auch von Anwendungen konsumiert werden, die außerhalb von OSGi laufen. Listing 1 zeigt, wie der gesamte Lebenszyklus des Repositories in den doStart()– und doStop()-Methoden des Activators gesteuert wird.

public abstract class OM { public static final String BUNDLE_ID = "org.gastro.server"; public static final OMBundle BUNDLE = OMPlatform.INSTANCE.bundle(BUNDLE_ID, OM.class); public static final OMTracer DEBUG = BUNDLE.tracer("debug"); public static final OMLogger LOG = BUNDLE.logger(); private static IAcceptor acceptor; public static IRepository repository; public static final class Activator extends OSGiActivator { public Activator() { super(BUNDLE); } @Override protected void doStart() throws Exception { OM.LOG.info("Gastro server starting"); EmbeddedDataSource dataSource = new EmbeddedDataSource(); dataSource.setDatabaseName("/gastro"); dataSource.setCreateDatabase("create"); IMappingStrategy mappingStrategy = CDODBUtil.createHorizontalMappingStrategy(); IDBAdapter dbAdapter = new EmbeddedDerbyAdapter(); IDBConnectionProvider dbConnectionProvider = DBUtil.createConnectionProvider(dataSource); IJDBCDelegateProvider jdbcDelegateProvider = CDODBUtil.createPreparedStatementJDBCDelegateProvider(); IStore store = CDODBUtil.createStore(mappingStrategy, dbAdapter, dbConnectionProvider, jdbcDelegateProvider); Map<String, String> props = new HashMap<String, String>(); props.put(IRepository.Props.OVERRIDE_UUID, "gastro"); props.put(IRepository.Props.SUPPORTING_AUDITS, "true"); props.put(IRepository.Props.VERIFYING_REVISIONS, "false"); props.put(IRepository.Props.CURRENT_LRU_CAPACITY, "100000"); props.put(IRepository.Props.REVISED_LRU_CAPACITY, "10000"); repository = CDOServerUtil.createRepository("gastro", store, props); CDOServerUtil.addRepository(IPluginContainer.INSTANCE, repository); CDOServerUtil.prepareContainer(IPluginContainer.INSTANCE); acceptor = (IAcceptor)IPluginContainer.INSTANCE.getElement("org.eclipse.net4j.acceptors", "tcp", "0.0.0.0:2036"); OM.LOG.info("Gastro server started"); } @Override protected void doStop() throws Exception { OM.LOG.info("Gastro server stopping"); LifecycleUtil.deactivate(acceptor); LifecycleUtil.deactivate(repository); OM.LOG.info("Gastro server stopped"); } } } 

Die ersten neun Zeilen von doStart() erzeugen einen DBStore mit horizontaler Mapping-Strategie und einer Derby DataSource. Dieser DBStore und eine Map mit zusätzlichen Properties werden verwendet, um das gastro Repository zu erzeugen. Die Repository-Instanz wird dann in einem zentralen Container abgelegt, dessen Funktion vage an die OSGi Service Registry erinnert, allerdings funktioniert er auch stand-alone. Der prepareContainer()-Aufruf fügt dem Container noch eine Factory für das CDOProtocol hinzu. Diese wird benötigt, damit vom Netzwerk eingehende Verbindungen mit benannten Repositories im Container assoziiert werden können. Die letzte Zeile führt dann zur Erzeugung eines TCPAcceptors innerhalb des Containers, wobei die dafür notwendigen Factories vorher von der Plugin Extension Registry automatisch registriert wurden (daher auch der Name IPluginContainer). Die doStop()-Methode deaktiviert zuerst den Acceptor und dann das Repository. Die GastroServer-Applikation ist fast leer, denn sie dient OSGi nur als Schnittstelle zur Aktivierung des Bundles und damit zur Ausführung der zuvor besprochenen Lebenszyklusmethoden des Activators. In der plugin.xml wird die Application folgendermaßen registriert:

<extension id="app" point="org.eclipse.core.runtime.applications" name="GastroServer"> <application cardinality="1" thread="main"> <run class="org.gastro.server.GastroServer"/> </application> </extension> 

Zum einfachen Starten einer Testinstanz des Servers liegt eine Startkonfiguration im org.gastro.server-Projekt. Sie startet eine Eclipse-Runtime-Instanz mit dem Server-Bundle sowie allen davon benötigten Bundles. Insgesamt enthält diese Minimalkonfiguration nur 23 Bundles, allerdings werden in späteren Iterationen noch einige dazukommen. In der Startkonfiguration ist das OSGi Tracing eingeschaltet, was beim Starten der Anwendung und in deren Betrieb zu erheblich mehr Ausgabe auf der Konsole führt und die Suche nach möglichen Fehlern erleichtern kann. Einmal gestartet, wird die GastroServer-Applikation über die OSGi-Konsole gesteuert. So führt zum Beispiel die Eingabe von close zum sicheren Herunterfahren der Application und zum Beenden des OSGi-Frameworks. Sollte der Repository-Prozess einmal hart beendet werden, ohne vorher mit close sicher heruntergefahren worden sein, dann wird der DBStore beim nächsten Start einen Crash erkennen und eine automatische Recovery einleiten. Andere Store-Typen können sich diesbezüglich jedoch anders verhalten.

Zum Start und Betrieb des Repository-Servers müssen keine EMF-Modelle deployt werden. Obwohl dies neuerdings auch möglich ist, falls nämlich generierter Code serverseitig benötigt wird, kommt der GastroServer mit der rein reflektiv arbeitenden Voreinstellung von CDO aus. Dabei werden neue Modelle im Betrieb und bei Bedarf von den Clients übertragen und dynamisch gespeichert. Dies geschieht für die Clients immer transparent, sie müssen lediglich ihre Modelländerungen innerhalb von Transaktionen ausführen, und CDO überträgt neue EPackages automatisch beim Commit auf den Server.

Es gibt viele Varianten, ein CDO Repository aufzusetzen, einige davon sind möglicherweise sogar eleganter als die gewählte. Zum Beispiel demonstriert die mit CDO ausgelieferte CDOServerApplication die Anwendung der beiden Hilfsklassen RepositoryConfigurator und TransportConfigurator, die das gesamte Serversetup aus einer XML-Datei laden. Auch das Spring Framework oder OSGi Declarative Services sind durchaus geeignet. Der Tag ist gerade erst angebrochen und das Repository läuft schon. Der Nachmittag wird in Vorbereitung auf den morgigen Modellierungstag dazu verwendet, sich noch einmal gedanklich mit den Fachkonzepten zu beschäftigen.

[ header = Seite 2: Tag 3: Die Modellierung ]

Tag 3: Die Modellierung

Es wird entschieden, das Gesamtmodell auf zwei Ecore-Dateien aufzuteilen: eine für das Stammdatenmodell und eine für die Konzepte des Tagesgeschäfts, von denen jede in einem eigenen Plug-in-Projekt liegt. Diese Modularisierung verhindert schon zur Entwicklungszeit unerwünschte Referenzen von den Stammdaten auf die Bewegungsdaten. Die beiden Projekte werden mit dem Empty EMF Project Wizard erzeugt und heißen org.gastro.inventory und org.gastro.business. Die Ecore-Dateien werden im jeweiligen model Folder zusammen mit den Diagrammdateien vom EcoreTools Wizard erstellt. Das inventory-Modell (Abb. 2) definiert die Klasse Restaurant und abstrahiert die Lagerhaltung, den Rezeptkatalog, die Restaurantstationen und ihre Mitarbeiter sowie die Menge der verfügbaren Speisekarten und Tische. Nichts CDO-Spezifisches muss in dieser Phase berücksichtigt werden, alles ist reine Businessmodellierung mithilfe von MOF-Metakonzepten, also Ecore. Das darauf aufbauende business-Modell (Abb. 3) definiert als Zentralkonzept den Geschäftstag. Für die Tagesplanung können den Tischen Servicekräfte zugeordnet und eine Tageskarte ausgewählt werden. Für die Durchführung des Tagesgeschäfts können Bestellungen an einem Tisch erfasst und mit Angeboten aus der Tageskarte verknüpft werden. Die zu referenzierenden Klassen aus dem inventory-Modell werden im EcoreTools-Editor einfach von der Outline View auf die Diagrammfläche gezogen und dann ganz normal verwendet. Vorsicht: Bidirektionale Referenzen zwischen inventory– und business-Konzepten sind in dieser Phase zwar technisch möglich, führen aber spätestens nach der Generierung zu Kompilierfehlern im resultierenden Java-Code!

Abb. 2: Das inventory-Modell

Abb. 3: Das business-Modell

Wenn die Businessmodellierung vorerst abgeschlossen ist, werden die zugehörigen Generatormodelle mit dem EMF Generator Model Wizard erzeugt. Dabei wird der auf der Model Import Page angebotene Ecore model (CDO Native) Importer ausgewählt, der eine Handvoll GenModel-Eigenschaften anpasst und damit den generierten Code CDO-kompatibel macht (siehe Erläuterungen zur Reflective Feature Delegation in Teil I). Sollte man dies beim Import des Generatormodells versäumt haben, hilft das CDO-GenModel-Migrator-Werkzeug bei der späteren Anpassung. Dann wird der Code für die Modelle und den Edit-Support generiert, sodass nun vier EMF-Plug-in-Projekte im Workspace liegen.

Der generierte Code muss nur an ganz wenigen Stellen von Hand modifiziert werden, um die Logik der modellierten EOperations und der berechneten EStructuralFeatures zu implementieren. Diese Logik wird ausschließlich gegen die generierten Interfaces geschrieben und hat somit keine CDO-Abhängigkeiten. Beim Betrachten dieser Interfaces fällt auf, dass sie CDOObject erweitern. Aber diese Voreinstellung des CDO Model Importers, bzw. Migrators kann leicht geändert werden. Will man verhindern, dass die Modellclients unbeabsichtigt Abhängigkeiten auf CDO erzeugen, muss nur die Root-Exends-Interface-Eigenschaft im GenModel zurück auf EObject gestellt und der Code regeneriert werden. Sogar Abhängigkeiten auf Ecore selbst könnten mit verschiedenen Generatoreinstellungen unterdrückt und reine POJOs generiert werden.

Die paar Methoden sind schnell implementiert und heute wird früh Feierabend gemacht.

[ header = Seite 3: Tag 4: Die Stammdatenverwaltung ]

Tag 4: Die Stammdatenverwaltung

Die generierten Modelle sollen natürlich gleich ausprobiert werden, und da der generische Repository-Server bereits läuft, muss lediglich eine neue Startkonfiguration für das generische User Interface von CDO erzeugt werden. Sie wird der Ordnung halber in einem eigenen Projekt namens org.gastro.testclient abgelegt und basiert auf einem Eclipse SDK Product, Net4j-Unterstützung für TCP Transport, dem CDO UI und, ganz wichtig, den vier zuvor generierten Modell und Edit-Plug-ins. Nach dem Start der Konfiguration erscheint eine Runtime Workbench mit der normalen Eclipse IDE, in der nun der CDO Sessions ViewPart geöffnet wird („ViewPart“, um der Verwechselung mit dem View-Konzept von CDO vorzubeugen).

Eine neue Session wird mit dem grünen Plus-Button geöffnet. Im folgenden Dialogfenster werden die Serververbindung tcp://localhost und der Name des Repositories gastro eingegeben. Außerdem kann hier bereits entschieden werden, ob die Package Registry der neuen Session automatisch mit der globalen Package Registry von EMF synchronisiert werden soll. In der Regel ist es empfehlenswert, das entsprechende Häkchen zu setzen, denn dann müssen die beiden gastro Packages später nicht manuell registriert werden. Sobald der Ok-Button geklickt wird, kann man die Serveraktivität auf der Konsole erkennen und die neue Session wird angezeigt. Ein kurzer Blick in die Package Registry der Session offenbart, dass die beiden gastro Packages der Session zwar bekannt sind, aber noch nicht im Repository persistiert wurden.

Nun wird per Rechtsklick auf die Session eine neue Transaction gestartet, in der dann neue Ressourcen erzeugt werden können. Eine Ressource hat in einem CDO Repository immer einen eindeutigen Pfad, der genauso aussieht wie der Pfad einer Datei im Workspace. Tatsächlich ist eine CDOResource Teil einer expliziten, hierarchischen Struktur aus CDOResourceFolders. Wie beim IWorkspace auch, können Clients via API auf diese Struktur zugreifen und sie dynamisch verändern. Beides geschieht in derselben CDO View oder Transaction, in der auch normale Objekte gelesen und modifiziert werden. Will man mit reinen EMF-Mitteln auf diese Struktur zugreifen, kann man dies über den URIConverter des verwendeten ResourceSets tun. Das URI-Format einer CDOResource ist stets cdo:// <repository-uuid>/<resource-path>.

Trotz der möglichen Dynamik der Folder-Struktur im Repository gehört diese normalerweise zum Entwurf der Anwendung und will wohlüberlegt sein. Daher wird entschieden, dass alle Restaurantdaten im Folder eDine liegen sollen. Darin soll genau eine eDine/inventory-Ressource existieren, und die Anwendung wird später pro Geschäftstag jeweils eine weitere Ressource mit einem Pfad nach dem Muster eDine/<JJJJMMTT> erzeugen. Die genaue Zuordnung der Ressourcen zu jeweils einem der beiden EPackages ist hier übrigens reiner Zufall, denn grundsätzlich kann jede Ressource Objekte aller registrierten Packages aufnehmen und Objekte in allen anderen Ressourcen referenzieren.

Nach dem Erzeugen der inventory-Ressource öffnet sich automatisch der generische CDO-Editor, im Gegensatz zu den gewohnten EMF-Editoren ist er aber zunächst leer. Die Einschränkung auf ein einzelnes Wurzelobjekt pro Ressource ist allein durch den sonst üblichen Wizard zur Erzeugung von XML-Ressourcen gegeben. Der CDO-Editor hingegen ermöglicht zu jedem Zeitpunkt die Erzeugung zusätzlicher Wurzelobjekte durch den Kontextmenüpunkt New Root. So werden nun das oberste Restaurant-Objekt, die Küche, die Theke, deren Mitarbeiter, die Produkte, Rezepte, Menükarten und Tische beispielhaft eingepflegt. Wer diese Arbeit etwas abkürzen will, kann stattdessen die Import-Resource-Funktion im Kontextmenü der Transaction ausprobieren: Die Datei inventory.xml liegt im testclient-Projekt und kann vom Dateisystem direkt in die Transaction importiert werden. In jedem Fall muss schlussendlich die Transaction committet werden, entweder durch den entsprechenden Kontextmenüeintrag der Transaction oder, noch einfacher, durch das Speichern des Editors.

Jetzt kann das Fachmodell live getestet, mit Situationen und Use Cases gespielt und aus Erfahrungen gelernt werden. Zu Testzwecken können separate Ressource-Strukturen gebildet und später zum Beispiel (ohne die sonst übliche Zerstörung möglicher Cross References) umbenannt oder einfach wieder gelöscht werden. Einzelne Ressourcen oder Gruppen können in XML-Dateien exportiert und später wieder in das Repository (oder in ein anderes) importiert werden. In der derzeitigen Repository-Konfiguration stehen via Rechtsklick auf die Session sogar die so genannten Audit Views zur Verfügung, mit denen man frühere Versionen des modellierten Objektgrafen im CDO-Editor betrachten kann.

In dieser Entwicklungsphase unterliegt das Modell, also die Struktur der Packages, erfahrungsgemäß mehr oder minder großen Änderungen. Das Thema der automatischen Datenmigration nach oder während der Modellevolution ist derzeit weder von EMF noch von CDO explizit adressiert. Es ist allerdings zu hoffen, dass das neue EMFT-Projekt Edapt auf diesem Gebiet schnelle Fortschritte machen wird und bald in CDO benutzt werden kann (Bugzilla 256856). Bis dahin ist nach jeder Änderung am Modell vorsichtshalber die Datenbank zu löschen und der Repository-Server neu zu starten. Falls existierende Daten gerettet werden sollen, müssen sie zuvor in XML-Ressourcen exportiert und vor dem Import in das neue Repository eventuell manuell migriert werden.

Der Testclient ermöglicht es außerdem, einige andere Eigenschaften von CDO zu demonstrieren, zum Beispiel die so genannten Passive Updates. Diese bewirken, dass das Repository CommitNotificationRequests an alle verbundenen Sessions (mit Ausnahme der Session, die die auslösende Transaction enthält) versendet. Dieses Verhalten ist voreingestellt, kann aber via Session API (dazu später mehr) oder im Kontextmenü des Editors jederzeit aus- und wieder eingeschaltet werden. Beim Wiedereinschalten wird die Session automatisch mit dem Repository resynchronisiert. Um die Auswirkungen zu testen, liegt dem org.gastro.testclient-Projekt die Startkonfiguration für eine zweite Laufzeitinstanz des Testclients bei. Einfach starten, Session öffnen, Transaction starten, die eDine/inventory-Ressource laden und die Vorteile eines Distributed Shared Models live erfahren.

Es wird entschieden, dass der generische CDO-Client zunächst zur Stammdatenverwaltung dienen soll, wodurch ein großer Teil dieses Tages für das Studium der CDO APIs übrig bleibt, die morgen endlich zum Einsatz kommen sollen.

[ header = Seite 4: Tag 5: Die Table-Anwendung ]

Tag 5: Die Table-Anwendung

Nun, da die Modelle ausreichend gereift und die clientseitigen Konzepte des CDO Frameworks wohlbekannt sind, wird die RCP Application für die Touchcomputer an den Restauranttischen in Angriff genommen. Zunächst wird mit dem RCP Application Wizard von PDE ein Rahmengerüstprojekt mit dem Namen org.gastro.rcp erstellt. Dieses Rahmengerüst liest die Laufzeitkonfiguration aus einer Properties-Datei und stellt sie den Fachanwendungen zur Verfügung:

public interface IConfiguration { public static final IConfiguration INSTANCE = org.gastro.internal.rcp.Configuration.INSTANCE; public String getPerspective(); public String getStation(); public String getServer(); public String getRepository(); public String getRestaurant(); public Date getBusinessDay(); } 

Die automatisch vom Wizard erzeugte Perspective und der ViewPart werden in ein neues Plug-in-Projekt namens org.gastro.rcp.table verschoben. Das Rahmengerüst steuert sie zur Laufzeit über den Wert von IConfiguration.INSTANCE.getPerspective() an. So ist die Geschäfts- und Präsentationslogik vollständig von den Plattformaspekten getrennt. Aus dem gleichen Grund wird im Plattform-Plug-in org.gastro.rcp eine Modellfassade eingerichtet, die den Anwendungen Lesezugriff auf das konfigurierte Restaurant-Objekt (Mandantenfähigkeit!) und die aktuelle BusinessDay-Instanz zur Verfügung stellt. Außerdem wird ein spezielles Pattern für transaktionale Modellmodifikationen angeboten, das die gesamte Transaktionsverarbeitung kapselt und vor den Anwendungen verbirgt. Die Idee ist, dass alle Operationen, die über die EMF APIs hinausgehen und daher eine Abhängigkeit auf CDO erfordern, in die Implementierung der Modellfassade im internen Teil des Plattformprojekts verschoben werden. Die Anwendungen können dann ausschließlich gegen EMF und dieses IModel-Interface programmiert werden:

public interface IModel { public static final IModel INSTANCE = org.gastro.internal.rcp.Model.INSTANCE; public AdapterFactory getAdapterFactory(); public Restaurant getRestaurant(); public BusinessDay getBusinessDay(); public Station getStation(); public <T extends CDOObject> Object modify(T object, ITransactionalOperation<T> operation); public interface ITransactionalOperation<T extends CDOObject> { public Object execute(T object); } } 

Dieser Ansatz der prinzipiellen Trennung der Lese- von den Schreibzugriffen ist gut geeignet für Programme mit mehreren Threads. Das Öffnen vieler Transactions zur sicheren Bearbeitung einiger Objekte ist nicht sehr teuer, da CDO intern die eigentlichen Objektdaten in der gemeinsamen Session speichert. Listing 2 zeigt, wie die CDO-Session und die globale View geöffnet werden. Die letzte Zeile installiert eine von der Voreinstellung abweichende Change Subscription Policy in der View. Sie bewirkt, dass jeder EMF-Adapter, der an ein Objekt geheftet wird, das von dieser View geladen wurde, automatisch eine Change Subscription beim Repository registriert. So kann der normale EMF-Mechanismus (MOF-style Notifications) benutzt werden, um sich über Remote-Änderungen an den Objekten informieren zu lassen.

@Override protected void doActivate() throws Exception { String server = IConfiguration.INSTANCE.getServer(); String repository = IConfiguration.INSTANCE.getRepository(); IConnector connector = (IConnector)IPluginContainer.INSTANCE.getElement("org.eclipse.net4j.connectors", "tcp", server); CDOSessionConfiguration config = CDONet4jUtil.createSessionConfiguration(); config.setConnector(connector); config.setRepositoryName(repository); session = config.openSession(); view = session.openView(); view.options().addChangeSubscriptionPolicy(CDOAdapterPolicy.ALL); } 

In Listing 3 ist zu sehen, wie mithilfe der CDO APIs das Restaurant-Objekt geladen und, falls nötig, in einer eigenen Transaction erzeugt wird. Hierbei ist wichtig, dass im Weiteren nicht die von createResource() erzeugte transaktionale Ressource verwendet wird, sondern die von view.getResource() zurückgegebene.

public synchronized Restaurant getRestaurant() { if (restaurant == null) { String name = IConfiguration.INSTANCE.getRestaurant(); String path = name + "/inventory"; if (!view.hasResource(path)) { CDOTransaction transaction = session.openTransaction(); Restaurant restaurant = InventoryFactory.eINSTANCE.createRestaurant(); restaurant.setName(name); try { CDOResource resource = transaction.createResource(path); resource.getContents().add(restaurant); transaction.commit(); } finally { transaction.close(); } } CDOResource resource = view.getResource(path); restaurant = (Restaurant)resource.getContents().get(0); } return restaurant; } 

Der generische Mechanismus für transaktionale Modifikationen am Objektgrafen ist in Listing 4 wiedergegeben. Er öffnet eine neue Transaction, bringt ein beliebiges Objekt aus der globalen View in den Transaktionskontext, führt die Business Operation mit dem kontextualisierten Objekt aus, committet die Transaction und führt das Ergebnis in den globalen Kontext zurück.

public <T extends CDOObject> Object modify(T object, ITransactionalOperation<T> operation) { CDOTransaction transaction = session.openTransaction(); try { T transactionalObject = transaction.getObject(object); Object result = operation.execute(transactionalObject); transaction.commit(); if (result instanceof CDOObject) { return view.getObject((CDOObject)result); } return result; } finally { transaction.close(); } } 

So viel zum Rahmengerüst. Die eigentliche Table-Anwendung wird damit recht schlank, denn sie besteht im Wesentlichen aus der TableView, die mit einem Werkzeug grafisch entworfen wird. Sie enthält ein Nebula PShelf Widget mit vier PShelfItems für die verschiedenen Funktionsbereiche der Table-Anwendung: Speisenauswahl, Bestellung, Bezahlung und Serviceruf. Die Bereiche verwenden zum Teil TreeViewers, die über EMF ContentProviders direkt auf dem Objektgrafen der globalen View navigieren. Die dabei intern zum Einsatz kommenden EMF ItemProviderAdapters führen automatisch zu CDOChangeSubscriptions und synchronisieren die Benutzerschnittstelle mit dem Repository. Abbildung 4 zeigt den Speisenauswahlbereich. Die hier ausgewählten Angebote werden zunächst in einer transienten Order-Instanz verwaltet, die im Bestellbereich angezeigt wird. Dort kann die Bestellung auch „abgeschickt“ werden.

Abb. 4: Speisenauswahl und Bestellung in der Table-Anwendung

Die dafür zuständige Methode (Listing 5) benutzt den IModel.modify()-Mechanismus, um das bis jetzt transiente Order-Objekt zu persistieren. Dabei wird die BusinessDay-Instanz explizit gelockt, um anderen Transactions die Modifikation zu verbieten und eine korrekte Bestellnummer berechnen zu können. Dies verstößt zwar gegen die Regel, dass in der Anwendungslogik kein CDO API benutzt wird, aber das kann später durch ein Refactoring behoben werden.

protected void sendOrder() { ITransactionalOperation<BusinessDay> operation = new ITransactionalOperation<BusinessDay>() { public Object execute(BusinessDay businessDay) { businessDay.cdoWriteLock().lock(); EList<Order> orders = businessDay.getOrders(); order.setNumber(getNextOrderNumber(orders)); orders.add(order); return null; } }; IModel.INSTANCE.modify(IModel.INSTANCE.getBusinessDay(), operation); createNewOrder(); } 

Dieser Tag war aufregend und vollgepackt mit Neuem, aber dafür steht auch schon das Rahmengerüst für die morgige Anwendung.

[ header = Seite 5: Tag 6: Die Department-Anwendung ]

Tag 6: Die Department-Anwendung

Nach dem Vorbild der Table-Anwendung wird nun die RCP-Anwendung im org.gastro.rcp.department-Projekt entwickelt, die die Abläufe an der Theke und in der Küche unterstützen. Sie besteht diesmal aus zwei ViewParts (Abb. 5), einem zur schnellen Mitarbeiterauswahl (hier wird das Nebula ImageGallery Widget verwendet) und einem, der die bestellten Gerichte auflistet, die vom ausgewählten Mitarbeiter zubereitet werden sollen. Die Bestellungen sind nach Tischen gruppiert und ihr Status kann durch einfachen Klick von „bestellt“ auf „serviert“ verändert werden. Sind alle Bestellungen eines Tisches serviert, verschwindet der Tisch aus der Liste. Der technische Grundaufbau unterscheidet sich nicht sehr von der Table-Anwendung, lediglich die ContentProviders sind etwas aufwändiger, um nur die anzuzeigenden Daten aus dem Objektgrafen der globalen View zu extrahieren. Die einzige Modellmodifikation ist das Wechseln des Bestellstatus. Sie wird wieder mit der IModel.modify()-Methode durchgeführt.

Abb. 5: Department-Anwendung und Webanbindung

Viel grafisches Schnittstellendesign und Präsentationslogik prägten diesen Tag. Am Ende läuft die Anwendung, und damit ist das gesamte RCP-Subsystem fertig implementiert.

Tag 7: Die Webanbindung

Im org.gastro.server.web-Projekt wird das GastroServlet entwickelt, das zusammen mit dem Jetty Bundle und der Equinox Servlet Registry in den GastroServer deployt wird. Es baut in der init()-Methode eine Session zum Repository auf, die diesmal statt eines TCPConnectors den wesentlich schnelleren JVMConnector benutzt. Danach wird eine CDOView geöffnet und die Menükarte des aktuellen Tages geladen. Die doGet()– und doPost()-Methoden des Servlets führen folgenden Code aus:

PrintWriter writer = resp.getWriter(); try { String html = template.generate(getMenuCard()); writer.print(html); } finally { IOUtil.close(writer); } 

Die Transformation des Modellgrafen nach HTML geschieht dabei in einem einfachen JET Template, wobei die Transformationslogik direkt auf dem globalen Objektgrafen navigiert. Änderungen an diesem Grafen durch die Rich Clients oder die Stammdatenverwaltung werden zwar im Browser nicht sofort angezeigt, aber von der CDOView im Servlet registriert und bei der nächsten Request-Verarbeitung berücksichtigt. Abbildung 5 zeigt im Hintergrund das Ergebnis. Es ist geschafft. Alle Systemkomponenten wurden pünktlich fertiggestellt und der Rest des Tages wird mit ausgiebigen Integrationstests verbracht.

Fazit

Durch den Einsatz der Modellierung konnten bereits in einer frühen Projektphase spielerisch Erfahrungen mit den Fachkonzepten gesammelt werden. Die Generierung der Modellklassen erzeugte hochqualitativen und performanten Code, den man kaum in dieser Menge programmieren könnte. Da die generierten Klassen zudem auf der Basisklasse CDOObjectImpl aufbauen, musste kein komplexer Code zur Behandlung der orthogonalen Aspekte des Modells geschrieben werden. Die Modellgröße zur Laufzeit musste nicht berücksichtigt werden, da CDO alle Objekte einzeln bei Bedarf lädt und automatisch wieder aus dem Speicher entfernt. Auch Transaktionalität, Verteilung und Persistenz wurden komplett von CDO übernommen und mussten lediglich konfiguriert oder angesteuert werden. Im Bereich der grafischen Benutzerschnittstelle wurde die meiste Arbeit von PDE Wizards und mit einem Widget-Designer-Tool erledigt. Rückblickend wird festgestellt, dass der Code, der von Hand geschrieben werden musste, fast ausschließlich der Geschäfts- und Präsentationslogik dient, wobei wiederum ausschließlich die generierten Businessinterfaces verwendet und alle Technologieabhängigkeiten an einem zentralen Ort im Rahmengerüst gekapselt wurden. Auf diese Weise ist die wertvolle Geschäftslogik maximal abgeschirmt gegen ein eventuelles Austauschen der Basistechnologien.

Ausblick

Obwohl CDO bereits eine Fülle an Funktionen bietet (viele davon konnten hier nicht zur Sprache kommen) und flexibel erweiterbar ist, stehen einige wichtige neue Features ganz oben auf der To-do-Liste. Da wäre zum einen der Offline Mode, der es ermöglichen wird, Teilgrafen aus einem Repository auszuchecken, dann über einen längeren Zeitraum ohne Konnektivität zu modifizieren und später mit dem Repository zu resynchronisieren. Der Wechsel zwischen Online Mode und Offline Mode wird manuell, aber auch automatisch nach Netzwerkverfügbarkeit möglich sein. Ganz wichtig ist auch die geplante Unterstützung für Legacy-Modelle, also solche, die nicht für CDO generiert wurden und auch nicht regeneriert werden können. Prominente Vertreter sind Ecore selbst, UML2, OCL und das GMF Notation Model, die Ecore erweitern. Parallel dazu hat das UML2-Projekt angekündigt, eine CDO-fähige Version seines UML-Metamodells zu entwickeln und zu pflegen.

CDO besitzt schon ein ausgeklügeltes Query-Framework, das einen Query String und Parameter zum Repository transportiert, dort durch steckbare Query Handler auswertet und die Ergebnisse entweder synchron oder sogar asynchron zurück überträgt. Allerdings fehlt noch ein einheitlicher, also vom Backend unabhängiger Query Language Handler. Ein solcher soll ebenfalls im Zyklus nach dem Galileo-Release auf der Basis von OCL entwickelt werden. Darüber hinaus gibt es eine Fülle an Funktionen und neuen Integrationen auf der Wunschliste, darunter Branching im Repository, Model Evolution, neue Backend-Anbindungen und leichter wiederverwendbare UI-Komponenten, wie Data Binding und Common Navigator Support. Über Unterstützung bei der Umsetzung freuen wir, das CDO-Team, uns natürlich immer und garantieren ein spannendes und abwechselungsreiches Betätigungsfeld für angehende Eclipse Committer.

Eike Stepper ist der ursprüngliche Autor von CDO und Net4j und leitet das inzwischen achtköpfige, internationale Committer-Team. Mit seiner 1991 gegründeten Firma ES-Computersysteme konnte er in vielen Projekten Erfahrungen als Entwickler und Berater sammeln. Themen um Modeling, OSGi-Softwaredesign und „gutes Entwickeln“ faszinieren ihn immer wieder.
Geschrieben von
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.