© DrHitch/Shutterstock.com
Xtend beyond Java

3 Beschleunigte Entwicklung von C++- und Qt-basierten Anwendungen - Teil 2


Im zweiten Kapitel habe ich bereits einen groben Überblick über die DSL gegeben [1]. Jetzt sehen wir uns das Resultat in einem Projekt genauer an.

Eine Vorbemerkung: Eine DSL bezieht sich immer, wie „domain-specific“ bereits ausdrückt, auf bestimmte projektspezifische Annahmen, Regeln oder Use Cases. In unserem Fall handelt es sich um mobile Businessanwendungen, wo Stamm- und Bewegungsdaten vom Server abgerufen und erfasste bzw. geänderte Daten per REST-API an den Server zurückgesandt werden. Ein Offlinemodus muss voll gewährleistet sein, damit Mitarbeiter (z. B. Servicetechniker) unabhängig und transparent arbeiten können – auch wenn gerade kein Netz vorhanden ist. Eine weitere wichtige Annahme in dieser DSL: Alle Daten können im Speicher gehalten werden, wobei durch die Verwendung von C++-Pointern des Typs QObject* der Speicherbedarf gering ist, da auch im UI (QML) mit den Pointern gearbeitet wird, also kein By-Value-Mapping erfolgt. Selbst das Einlesen größerer Datenmengen ist problemlos durch asynchrones „Nachladen“ umgesetzt, worauf ich aber in diesem Kapitel nicht näher eingehe. Es gibt andere Situationen, wo die Daten nicht In-Memory verfügbar sind, sondern immer per Query aus der SQLite gelesen werden – aber das ist dann eine andere DSL. Das Datenmodell und das Eclipse-Projekt befinden sich auf GitHub. Es ist ein simples kleines Projekt, das folgende Daten enthält (Abb. 3.1):

  • Aufträge und Auftragspositionen
  • Kunden, die einem Auftrag zugeordnet werden können
  • Schlagworte, die einem Auftrag als Suchkriterien zugewiesen werden können
gentz_1.jpg

Abbildung 3.1: Beispielprojekt: Beziehungen

Einige Besonderheiten habe ich eingebaut, die typisch für Businessanwendungen sind. Einige Daten existieren nur im Zusammenhang mit anderen, manche Daten werden vom Server empfangen, dienen aber nur als „Look-up“-Daten, die nicht am mobilen Gerät geändert werden. Die vom Server übertragenen Datenmengen können sehr unterschiedlich sein, ebenfalls die Datenstrukturen. Hier ein paar Informationen, welche Besonderheiten in das Beispielprojekt eingebaut wurden (Abb. 3.2):

gentz_2.jpg

Abbildung 3.2: Beispielprojekt: Persistierung

  • Positionen sind Bestandteil von Aufträgen, d. h., sie können nicht alleine existieren. Wird ein Auftrag gelöscht, werden auch alle Positionen kaskadierend gelöscht.
  • Schlagworte werden in der mobilen App nicht geändert, sind also nur „read-only“.
  • Kundendaten sind sehr groß und werden daher in einer SQLite-Tabelle gecacht. Alle anderen Daten werden direkt als JSON gespeichert

Das Datenmodell

Das Datenmodell des Beispielprojekts ist schnell erfasst (Abb. 3.3).

gentz_3.jpg

Abbildung 3.3: DSL-Datenmodell

Die projektspezifischen Besonderheiten finden sich im Datenmodell wieder. Die Beziehung zwischen Auftrag und Positionen ist im Auftrag:

ref cascade Position [1..*] positionen opposite auftragsKopf;

und in den Positionen:

ref Auftrag auftragsKopf opposite positionen;

Der Auftrag beinhaltet die Positionen, ist also für deren Lebenszyklus verantwortlich, was durch das Schlüsselwort cascade festgelegt wird. Es muss zu einem Auftrag mindestens eine Position angelegt sein [1..*]. Beide Referenzen kennen sich gegenseitig durch die opposite-Angabe. Natürlich unterstützt uns der Editor bei der Eingabe, wobei die Attribute komfortabel ausgewählt werden können.Aufträge haben ebenfalls eine zu-n-Beziehung [0..*] zu den Schlagworten. Schlagworte sind aber unabhängige Objekte, und daher gibt es keine opposite- oder cascade-Angaben:

var Schlagwort [0..*] tags;

Wie schon im ersten Kapitel beschrieben, nutzt die DSL als Basis die DTO-DSL von Lunifera. Ergänzende Informationen werden als Annotations integriert. So finden wir an den Schlagworten:

@CachePolicy("-R-")

Im Normalfall wird davon ausgegangen, dass Daten von der App verändert werden, und beim Beenden der App diese Änderungen wieder gespeichert werden, entweder als JSON oder in einer SQLite-Tabelle. Die Angabe der Cache Policy -R- legt fest, dass diese nur „read-only“ und ohne automatische Speicherung verfügbar sind.

Der Offlinecache selbst ist in der App normalerweise als JSON-Datei implementiert. JSON-Dateien werden vom BlackBerry10 OS sehr performant gelesen und geschrieben, solange die Datenmenge überschaubar ist. Fünf- bis zehntausend Datensätze ist hier eine grobe Richtschnur. Größere Datenmengen werden besser in einer SQLite-Tabelle gespeichert. Es kann aber durchaus Situationen geben, wo es sich um komplexe Baumstrukturen handelt und wo eine JSON-Datei auch bei größeren Datenmengen überlegen ist, da keinerlei komplexe Mappings (JOINs) erforderlich sind und die JSON-Datei direkt als Qvariant Map zur Verfügung steht.

In unserem Beispiel haben wir festgelegt, dass die Kunden in der SQLite gespeichert werden können, was durch diese Annotation erreicht wird:

@SqlCache("")

Manchmal ändern sich Annahmen oder Voraussetzungen, sodass vielleicht nach einem halben Jahr bei einer Klasse das Caching von JSON auf SQLite oder umgekehrt umgestellt wird. In „normalen“ Projekten ist dann erst einmal einiges an Coding und Refactorings angesagt. Mit einer DSL ändern wir nur eine Annotation und erhalten alle entsprechenden Methoden neu generiert. All das ist so gelöst, dass sich die Aufrufe aus dem UI (QML) nicht ändern und alle Logiken und Aufrufe in C++ entsprechend generiert werden. Vom Auftrag zum Kunden haben wir noch eine zu-1-Beziehung:

ref lazy Kunde...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang