enhancement
major
minor
defect
critical
major
minor
major
#30225
Modell: Handler, die für ein Attribut (Änderung) oder einen Typ (Erstellung, Löschung) deklariert sind, werden innerhalb des Commit ausgeführt
Siehe auch: #30218.
Problem
Eine Anwendung, die Daten konsistent halten muss, sobald sich ein bestimmtes Attribut ändert – unabhängig davon, wer die Änderung vornimmt (ein Ansichtsformular, eine Tabellenbearbeitung, ein Import, ein Skript) –, benötigt einen ModificationListener. Ein solcher Listener kann nur in Java registriert werden (KnowledgeBase.addModificationListener, typischerweise über einen Service der Anwendung), wird bei jedem Commit jeder Sitzung aufgerufen und muss die erstellten, aktualisierten oder entfernten Zuordnungen selbst nach Typ und Attribut filtern. Die aktualisierte Map gibt nicht einmal an, welche Spalten eines Elements sich geändert haben, und eine in einer Verknüpfungstabelle gespeicherte Referenz ändert Zeilen einer anderen Tabelle als die des zugehörigen Objekts.
Das Modell bietet bereits einen deklarativen, typbezogenen Hook für die Erstellung: Die für einen Typ deklarierten `TLObjectInitializer` werden vom `ModificationListener` des `ModelService` auf neue Objekte angewendet. Für Änderungen oder das Löschen eines bestehenden Objekts gibt es kein entsprechendes Gegenstück.
Beispiel: E-Home (haumacher/ehome #111) schreibt jeden Ausdruck um, der einen Regelparameter benennt, wenn die ID des Parameters umbenannt wird.
Lösung
Änderungshandler für ein Attribut
Die Attribut-Annotation <change-handlers> (TLChangeHandlers) listet Handler (TLChangeHandler) auf, die aufgerufen werden, wenn eine Transaktion, die den Wert des annotierten Attributs eines bestehenden Objekts ändert, festgeschrieben wird – unabhängig von der Ursache der Änderung. changed(object, attribute, oldValue, newValue, CommitActions) wird innerhalb des Commits vor den Commit-Prüfungen (Modellbeschränkungen) ausgeführt; seine Änderungen werden Teil desselben Commits.
- Der alte Wert ist der festgeschriebene Wert; ein Wert, der innerhalb der Transaktion gesetzt und wieder zurückgesetzt wird, stellt keine Änderung dar. Objekte, die in derselben Transaktion angelegt oder gelöscht werden, werden nicht gemeldet.
- Unterstützt werden alle Attribute, deren Werte in der Datenbank gespeichert sind: Spalten (auch flexible Attribute), Verknüpfungstabellen, in den Zeilen der referenzierten Objekte gespeicherte Referenzen, formatierter Text sowie die umgekehrten Enden von Referenzen (ein Handler für eine umgekehrte Referenz wird sowohl für das alte als auch für das neue referenzierte Objekt aufgerufen).
- Beim Laden des Modells werden <change-handlers> für ein Attribut verworfen, dessen Änderungen nicht erkannt werden können (abgeleitete, vorübergehende, berechnete Werte). Dazu überprüft der Modell-Loader alle Annotationen, die TLAnnotationTargetCheck implementieren, in einer späten Phase, wenn alle Teile vollständig sind.
- Handler von überschriebenen Attributen werden vererbt, sofern nicht „ignore-inherited“ gesetzt ist.
- Es werden nur Attribute überprüft, die Handler enthalten: Der Speicher des Attributs ermittelt anhand der Rohänderungen des Commits, welche Objekte sich möglicherweise geändert haben (StorageImplementation.addChangeLocators), indiziert nach Tabelle im Modell-Cache. Ein Modell ohne Handler verursacht keinen Aufwand.
Lebenszyklus-Handler für einen Typ
Die Typ-Annotation <lifecycle-handlers> (TLLifecycleHandlers) listet Handler (TLLifecycleHandler) auf, die über Objekte des Typs und seiner Spezialisierungen informiert werden (Handler von Generalisierungen zuerst):
- created(object, CommitActions): im Commit der erzeugenden Transaktion, nach den `TLObjectInitializer`s aller erzeugten Objekte, vor den Änderungs-Handlern und den Commit-Prüfungen.
- Änderung deleting(object, CommitActions): zweiphasig. Wird aufgerufen, wenn das Objekt gelöscht wird, während die Transaktion gesperrt ist (das Objekt kann gelesen, aber nicht geändert werden), und gibt die Änderungen zurück, die nach dem Entfernen des Objekts und seiner Kaskade innerhalb der Löschtransaktion ausgeführt werden.
- Objekte, die in derselben Transaktion erstellt und gelöscht werden, werden weder als erstellt noch als gelöscht gemeldet.
TL-Script
- <change-handlers><script operation="..."/></change-handlers> (ChangeHandlerByExpression): Das Skript erhält das Objekt, den neuen Wert, den alten Wert und das Attribut.
- <lifecycle-handlers><script created="..." deleting="..."/></lifecycle-handlers> (LifecycleHandlerByExpression): Das Löschskript liest das Objekt und gibt eine Funktion (oder nichts) zurück, die nach dem Löschen ausgeführt wird.
- Die Skripte werden ohne Sicherheitsprüfungen ausgeführt, ebenso wie die Modellbeschränkungen: Es handelt sich um im Modell deklarierte Konsistenzlogik.
Allgemeine Regeln
- Von Handlern vorgenommene Änderungen lösen keine Änderungs- oder Erstellungs-Handler erneut aus. Löschvorgänge werden bei jedem Löschen gemeldet, sodass ein von einem Handler gelöschtes Objekt an die Lösch-Handler seines Typs gemeldet wird.
- Ein Java-Handler informiert den Benutzer, indem er eine AfterCommitAction bei den CommitActions registriert, die er erhält (#30218). Es gibt keine spezielle Nachrichtenunterstützung für TL-Script-Handler.
- Die Initialisierer der erstellten Objekte werden in einer flachen Schleife angewendet, anstatt in einer Kette von ` Modification.andThen()`, deren Verschachtelung mit der Anzahl der erstellten Objekte zunahm.
- Entwicklerartikel „model/change-handlers“ (wann welcher Mechanismus zu verwenden ist, Zeitpunkt, Beispiele).
Vorbereitete Rohänderungen für ModificationListener
„ModificationListener.createModification“ erhält ein „PendingChanges“-Objekt anstelle der drei Maps mit erstellten, aktualisierten und entfernten Elementen. Zusätzlich zu diesen Maps gruppiert es die Elemente nach Tabellen und gibt für ein aktualisiertes Element an, welche Attribute (Spalten und flexible Attribute) sich geändert haben und welchen Wert sie bei der Festschreibung hatten – berechnet aus den festgeschriebenen und den transaktionslokalen Werten des Elements, ohne die `ItemChange`s des späteren `UpdateEvent` zu erstellen.
Migration
- ModificationListener.createModification(KnowledgeBase, CommitActions, Map created, Map updated, Map removed) wird zu createModification(KnowledgeBase, CommitActions, PendingChanges changes). Implementierungen ersetzen die drei Map-Parameter durch `PendingChanges ` und lesen die Maps über `changes.getCreated()`, `changes.getUpdated() ` und `changes.getRemoved()`.
- Eine Implementierung von `StorageImplementation`, die sowohl `ColumnStorage` als auch `SeparateTableStorage` implementiert, muss `addChangeLocators(...) ` und `canDetectValueChanges()` überschreiben, um beide Verhaltensweisen zu kombinieren (der Compiler meldet die widersprüchlichen Standardmethoden).