Vertec Objekte und ihre Anbindung an das UI-Modell

Erstellt: 14.07.2020, Änderung:
Mehr ansehen

Objekte in Vertec

In Vertec sind Daten wie Projekte, Rechnungen, Leistungen, aber auch Ordner, Listeneinstellungen etc. Objekte im Memory, die sich persistent in einer Datenbank speichern. Ob diese im Memory geladen sind oder nicht spielt für die Erläuterung hier keine Rolle; für das Objektsystem existieren nur Objekte im Memory der entsprechenden Vertec-Session.

Diese Objekte sind klassische Software-Objekte im OO (Object-Oriented) Sinn, tragen also auch Logik in sich (z.B. die MwSt einer Rechnung rechnet diese selber aus), und auch die anderen Eigenschaften von OO Systemen kommen zur Anwendung wie Vererbung, Polymorphismus über die Vererbung (Beispiel Adresseintrag) und Abstraktion (Beispiel Usereintrag). Wir nennen die Objekte, die in einer einzigen Vertec Session vorhanden sind, den Object Space.

Die Objekte tragen also die Daten auf sich und regeln ihre eigenen Verhältnisse, das ist praktisch für den Anwender dieser Objekte. Die Objekte leben aber ja nur in einer einzigen Session, also einem Object Space, wie ist das denn, wenn Daten ändern, Objekte gelöscht werden etc. in einer anderen Session? Hier kommt der Notif-Server ins Spiel.

Notif und Object Space Synchronization

Da die Objekte nur in einem Object Space leben, müssen sie sich irgendwie informieren, wenn etwas geändert wurde und damit also die Daten in einem Object Space out of date sind. Das geschieht über den Notif-Server.

Jede Vertec Session speichert regelmässig (standardmässig alle 30 Sekunden) die Objekte, die im lokalen Object Space geändert wurden und damit dirty sind, in die Datenbank. Gleichzeitig schickt sie eine Liste der Objekte, welche sie in der DB aktualisiert hat, an den Notif-Server und erhält von diesem eine Liste von Objekten zurück, die seit dem letzten Zugriff von anderen Sessions geändert wurden. Diese Liste gleicht die Session ab mit den Objekten, die im eigenen Object Space geladen sind, und setzt diese, falls vorhanden, out of date. Das bedeutet, dass diese Objekte sich beim nächsten Zugriff selber neu aus der Datenbank laden und damit den aktuellen Datenbestand besitzen.

Zentral ist dabei, dass der Notif-Server von jeder Session (jedem Client oder Object Space) auch dann alle 30 Sekunden aufgerufen wird, wenn er selber keine dirty Objekte zum Speichern hat. So kommen Änderungen von anderen Sessions auch bei Inaktivität des Users oder der Session durch.

Gut, die Objekte sind also nun im Object Space aktuell, aber wie kommen denn diese nun auf die Benutzeroberfläche (UI)?

Model View Controller Anbindung der Objekte an das UI

Wir verfolgen bei der Anbindung des Objects Spaces an das UI ein Pattern, das angelehnt ist an das Model View Controller (MVC) Pattern, siehe z.B. https://de.wikipedia.org/wiki/Model_View_Controller und folgende Grafik:

Wir sehen hier, dass die Daten im Modell (bei uns die Objekte im Object Space) durch einen Controller verändert werden, während der User eine "View" sieht, die sich vom Modell her aktualisiert. Dieser Pfeil "Update" beinhaltet die Logik, die nötig ist, dass der User mitbekommt, wenn im Modell etwas geändert wurde, was nicht direkt von ihm gemacht wurde (ein Side-Effekt von einer Änderung von ihm, von einem anderen User, etc.).

Wie ist dieses "Update" in Vertec genau umgesetzt?

Subscriptions

Schaut man den Pfeil "Update" in der obigen Grafik an, dann wirkt das auf den ersten Blick simpel. Werte wie z.B. ein Projekt-Code können aber an verschiedenen Orten auf der Oberfläche angezeigt werden. Vielleicht gibt es auch noch andere Werte, die von diesem Wert abhängen (z.B. der Speicherpfad des Projektes, oder der Inhalt des Projektauswahlfelds auf einer Leistung). So wird schnell klar, dass ein "Push" Mechanismus - wenn also das Modell die Datenänderungen nach vorwärts "pushen" würde - schnell an die Grenzen kommt. Der Pfeil ist denn auch eher als Pfeil der Datenfluss-Richtung zu verstehen und nicht als Richtung der Programmlogik.

Diesem Problem begegnen wir mit der Anwendung eines anderen Patterns, nämlich des "Publish/Subscribe" Patterns, hier Subscription genannt. Es gibt viele gute Übersichtsartikel, siehe z.B. https://www.ably.io/concepts/pub-sub.

Was macht nun eine Subscription? Als Beispiel nehmen wir etwas in Vertec, was auf einen Wert eines Objekts abstellt, z.B. das Feld mit dem Code auf der Projektseite, die Berechnung des Speicherpfads des Projektes oder eben ein Projektauswahlfeld auf einer Leistung. Dieses Element hat nun die Möglichkeit (und im Vertec Kontext die Pflicht), sich über einen komplexen Mechanismus auf Änderungen des Basiswertes zu "abonnieren" - zu "subscriben" - im Beispiel auf den Code eines Projektes. Wenn nun dieser tatsächlich ändert, dann geschieht nichts weiter, als das alle diese Subscriptions "feuern", d.h. die entsprechenden Elemente werden auf out of date gesetzt.

Das out of date setzen hat unmittelbar keinen weiteren Effekt, ausser dass das Element weiss, das es nicht aktuell ist. Wenn nun irgendwer (das UI direkt, ein Python Skript, via COM, XML etc.) auf das Element zugreift, dann weiss dieses, dass es sich neu berechnen muss, und tut dies, bevor es seinen neuen Wert zurückgibt und während der Neuberechnung wiederum die nötigen Subscriptions absetzt.

Dies das grundsätzliche System. Nun noch einige Fälle im Detail:

OCL Expressions

Jede Liste enthält ja viele OCL Expressions, von einfachen (z.B. code) bis zu komplexen (z.B. rechnungen->select(total>0)->size). Die OCL Expression setzt dabei automatisch die nötigen Subscriptions ab, damit die ganze Zelle out of date gesetzt werden kann, wenn sich etwas an den zugrundeliegenden Daten ändert. Im ersten Fall wird einfach das Code-Member auf dem entsprechenden Projekt subscribed. Der zweite Fall ist  komplexer, hier sind folgende Subscriptions nötig: 1. auf die Rechnungsliste des Projektes (es könnte ja eine neue Rechnung dazukommen oder eine bestehende gelöscht werden), 2. auf das Total jeder einzelnen Rechnung - das ist ein spannender Fall, der weiter unten beschrieben ist, weil das ein derived Attribut ist, also ein berechnetes Attribut (dessen Berechnung auch wieder viele Subscriptions nach sich zieht). Man sieht also, selbst in einfachen Fällen hat man schnell viele Subscriptions.

Derived Attributes

Das sind berechnete Attribute auf Objekten, die nicht in der Datenbank gespeichert werden, sondern ihre Werte zur Laufzeit berechnen. Damit lässt sich die Businesslogik (z.B. MwSt-Betrag einer Rechnung) schön kapseln.  Das Total im obigen Beispiel ist so ein Attribut. Da sich diese Attribute nur einmal beim ersten Zugriff berechnen, müssen sie alles subscriben, worauf die Berechnung beruht. Wenn sich an diesen Werten etwas ändert, wird das total out of date gesetzt, was wiederum die Subscription im obigen Beispiel triggert.

Setzt ein Derived Attrbut nicht alle benötigten Subscriptions ab, und ist dann also während der Laufzeit irgendwann nicht mehr aktuell, so reden wir von einem Subscription Fehler. In so einem Fall bringt man das Attribut nicht mehr dazu, sich neu zu berechnen, ausser man startet die Session neu. Subscription Fehler kann es aber natürlich auch anderen Stellen geben, der Entwickler muss bei der Implementation darauf achten, alle nötigen Subscriptions abzusetzen.

OCL Funktionen / Operatoren

Operatoren wie ->getArbeitszeit(von, bis) können in der Regel keine Subscriptions absetzen. In vielen Fällen ist es unmöglich, z.B. wenn das Datumsintervall über ein Datums-Literal Wert aufgerufen wird - darauf kann keine Subscription abgesetzt werden. Bei den OCL Call Operatoren ist es noch offensichtlicher, denn da müsste der, der das Python Skript schreibt, welches vom OCL Call Operator aufgerufen wird, alle Subscriptions absetzen, und das ist nicht möglich, weil es in den Python Methoden keinen Subscriber gibt.

Das bedeutet, dass sich die Ergebnisse solcher Funktionen nicht "automatisch" updaten. Will man hier sicher sein, aktuelle Daten zu haben, muss man die Funktionen bei Verwendung neu aufrufen.