CodamAIDocs
Themafertig

Hooks: Arten und Zeitpunkte

Welche Hook-Punkte es gibt (vor und nach der Datenbankänderung, je Operation) und wie ein Hook registriert wird.

Ausprägungen
CREATEUPDATEPATCHDELETEROLLBACKREAD (nach dem Laden, vor der Antwort)beforeafterReihenfolge per @OrderModell-Hook und Feld-Hook

Worum es geht

Ein Hook (englisch für „Haken“) ist eine Klasse in deinem Projekt, die CDMS an festen Stellen eines Standardablaufs aufruft. So hängst du eigene Fachlogik ein, ohne generierten Code anzufassen: eine Kundennummer vergeben, eine fachliche Regel prüfen, ein Feld für die Antwort entschlüsseln.

Diese Seite beschreibt den Modell-Hook. Er gehört immer zu einem Modell. CDMS ruft ihn für jedes Objekt dieses Modells auf, das eine Anfrage anlegt, ändert, löscht, zurücksetzt oder liest. Dabei sagt es dem Hook, welche Operation gerade läuft. Logik für einen einzelnen Wert, die du an vielen Feldern brauchst, etwa Verschlüsseln, ist ein Feld-Hook.

Einen Hook schreiben und registrieren

Ein Hook ist eine Spring-Bean, die HookServiceInterface<…Entity> für die Entity des Modells implementiert:

@Service
@Order(100)
public class OrderHook implements HookServiceInterface<OrderEntity> {

  @Override
  public void beforeDatabaseChange(OrderEntity item, CmsMethods method)
      throws AbstractCodamaiException {
    if (method == CmsMethods.CREATE && item.getOrderNr() == null) {
      item.setOrderNr(nextNumber());
    }
  }

  @Override
  public void afterDatabaseChange(OrderEntity item, CmsMethods method)
      throws AbstractCodamaiException {
    if (method == CmsMethods.READ) {
      // e.g. convert a value for the response
    }
  }
}
  1. 1
    Entwickler
    schreibt die Klasse unter dem Basispaket des Projekts und macht sie mit @Service zur Bean
    Spring findet nur Beans unter com.codamai. Das Basispaket aus dem Projektrahmen liegt dort.
  2. 2
    Entwickler
    setzt @Order direkt an die Klasse, wenn die Reihenfolge mehrerer Hooks zählt
  3. 3
    CDMS
    sucht beim ersten Objekt eines Modells alle Beans, die HookServiceInterface für genau diese Entity implementieren
  4. 4
    CDMS
    sortiert sie nach @Order und merkt sich die Liste
  5. 5
    Hook
    wird ab jetzt für jedes Objekt dieses Modells aufgerufen

Das Interface hat genau zwei Methoden: beforeDatabaseChange und afterDatabaseChange.

Im Hub kannst du einen Hook am Modell eintragen. Dieser Eintrag kündigt die Logik nur an. Was wirklich läuft, bestimmt allein die Klasse in deinem Projekt. Wo eigener Code sonst noch hingehört, steht unter Generierter Code und eigener Code.

Ein Hook gilt für die Entity in seinem Typparameter. Bei abstrakten Modellen arbeitet CDMS immer mit dem konkreten Untertyp, also läuft der Hook des Untertyps, etwa PrivatkundeEntity.

Die Operationen

Der zweite Parameter method sagt dir, was gerade passiert. Ein Hook bekommt alle Operationen und entscheidet selbst, auf welche er reagiert.

CmsMethodsausgelöst durchbeforeafter
CREATEAnlegen, auch jedes neue Kind in einem verschachtelten Schreibvorgangjaja
UPDATEPUT, auch jedes Kind, das über eine Beziehung mit UPDATE mitgeändert wirdjaja
PATCHPATCH, auch jedes Kind, das dabei mitgeändert wirdjaja
DELETELöschen, auch jedes abhängige Kind, das mitgelöscht wirdjaja
ROLLBACKZurücksetzen auf eine frühere Revisionjaja
READLesen per id, Suche, ausgebaute Referenzen, Zurücklesen nach dem Schreiben–ja

Eine Suche meldet sich ebenfalls als READ, genau wie das Lesen eines einzelnen Objekts.

Die Zeitachse eines Schreibvorgangs

sequenceDiagram
    participant C as Client
    participant D as CDMS
    participant H as Hook
    participant DB as Datenbank
    C->>D: POST /order/create
    D->>D: Rollen prüfen, Felder übertragen, Verstöße sammeln
    D->>H: before (CREATE)
    Note over H: darf das Objekt noch ändern
    D->>D: Verstöße erneut prüfen → sonst 422
    D->>DB: Objekt übergeben, die id wird vergeben
    D->>H: after (CREATE)
    D->>DB: flush: SQL ausführen
    D->>DB: zurücklesen
    D->>H: after (READ) für jedes gelesene Objekt
    D->>DB: commit
    D-->>C: 200 mit dem Objekt

Ein Schreibvorgang löst also meist zwei Arten von Hooks aus: die der Schreib-Operation und danach READ beim Zurücklesen für die Antwort. Nur DELETE liest nichts zurück. Die Schritte im Detail stehen unter Die Reihenfolge in einem Schreibvorgang.

before und after

Die beiden Hook-Punkte
before
beforeDatabaseChange
  • läuft, nachdem CDMS alle Felder übertragen hat, vor der Validierung
  • neue Objekte haben noch keine id
  • bei PUT und PATCH stehen schon die neuen Werte am Objekt
  • Änderungen am Objekt werden gespeichert und noch geprüft, wenn das Feld vorher einen Verstoß hatte
  • typisch: Werte ableiten, fachlich prüfen, ablehnen
after
afterDatabaseChange
  • läuft, nachdem CDMS das Objekt an die Datenbank übergeben hat, vor dem flush und vor dem Commit
  • neue Objekte haben ihre id
  • Änderungen am Objekt werden noch gespeichert, aber nicht mehr validiert
  • typisch: Folgeaktionen vorbereiten, die die id brauchen
  • bei READ: Werte nur für die Antwort anpassen

Beide Punkte liegen in der Transaktion. Wirft ein Hook einen Fehler, wird die ganze Anfrage zurückgerollt. Was dann beim Client ankommt, steht unter Wenn ein Hook scheitert.

Alle Operationen im Einzelnen

Was der Hook je Operation sieht

Wann: POST /create, oder ein neues Kind ohne id in einem verschachtelten Schreibvorgang

  1. 1
    Hook
    before: das neue Objekt mit den gesendeten Werten und den Defaultwerten, ohne id
  2. 2
    Hook
    after: dasselbe Objekt, jetzt mit id

Ergebnis: Ein before-Hook kann ein Pflichtfeld füllen, das der Client gar nicht kennt, etwa eine Auftragsnummer. Siehe Ein Objekt anlegen.

Wann: PUT /update/{id}, oder ein Kind mit id, das über eine Beziehung mit UPDATE mitgeändert wird

  1. 1
    Hook
    before: das gespeicherte Objekt, in das CDMS die gesendeten Werte schon übertragen hat
  2. 2
    Hook
    after: dasselbe Objekt nach der Übergabe an die Datenbank

Ergebnis: Den alten Wert eines Feldes siehst du am Objekt nicht mehr. Siehe Ersetzen mit PUT.

Wann: PATCH /update/{id}, oder ein Kind, das dabei mitgeändert wird

  1. 1
    Hook
    before: das gespeicherte Objekt mit den geänderten Feldern
  2. 2
    Hook
    after: dasselbe Objekt nach der Übergabe an die Datenbank

Ergebnis: Die Operation heißt PATCH, nicht UPDATE. Soll dein Hook bei jeder Änderung laufen, prüfe auf beide. Siehe Ändern mit PATCH.

Wann: DELETE /delete/{id}, jedes mitgelöschte abhängige Kind, und jedes abhängige Kind, das durch PUT oder PATCH aus seinem Elternobjekt fällt

  1. 1
    Hook
    before: das Objekt mit seinen Feldern, bevor es aus der Datenbank verschwindet
    Wirft der Hook einen Fehler, bleibt alles, wie es war.
  2. 2
    Hook
    after: dasselbe Objekt, zum Entfernen übergeben

Ergebnis: Siehe Der Ablauf eines DELETE.

Wann: POST {basis}/{id}/rollback/{revision}, nur für das angesprochene Objekt

  1. 1
    Hook
    before: das Objekt im aktuellen Stand
  2. 2
    CDMS→Database
    stellt die Revision wieder her
  3. 3
    Hook
    after: das Objekt im wiederhergestellten Stand

Ergebnis: Eine eigene Operation, damit dein Hook ein Zurücksetzen von einer normalen Änderung unterscheiden kann. Siehe Auf einen alten Stand zurücksetzen.

Wann: jedes Objekt, das CDMS aus der Datenbank lädt: Lesen per id, jede Zeile einer Suche, jede ausgebaute Referenz, das Zurücklesen nach dem Schreiben

  1. 1
    CDMS→Database
    lädt die angeforderten Felder
  2. 2
    Hook
    after: das geladene Objekt, bevor es zur Antwort wird
    Das Objekt enthält nur die Felder, die die Anfrage angefordert hat. Alle anderen sind leer.
  3. 3
    CDMS→Client
    Antwort mit den Werten, die der Hook hinterlassen hat

Ergebnis: Was der Hook hier ändert, landet nur in der Antwort, nicht in der Datenbank. Die Historie eines Objekts läuft nicht durch den READ-Hook. Siehe Ein Objekt lesen.

Mehrere Hooks auf einem Modell: @Order

Du darfst für ein Modell mehrere Hooks schreiben, etwa einen für die Nummernvergabe und einen für eine fachliche Prüfung. @Order legt ihre Reihenfolge fest: kleinere Zahl zuerst.

Zwei Hooks auf OrderEntity
  1. 1
    Hook
    @Order(10) NumberHook: vergibt die Auftragsnummer
  2. 2
    Hook
    @Order(20) CheckHook: prüft, ob der Auftrag zum Kunden passt, und sieht die Nummer schon

Die Reihenfolge gilt je Objekt: Für jedes Objekt laufen alle Hooks seines Modells nacheinander, dann kommt das nächste Objekt dran. In welcher Reihenfolge die Objekte eines verschachtelten Schreibvorgangs drankommen, steht unter Hooks bei verschachtelten Objekten und Kaskaden.

Entscheidungstabelle

Welcher Hook-Punkt passt?
Was du tun willstHook-Punkt
ein Feld ableiten oder ein Pflichtfeld füllenbefore, CREATE (und UPDATE/PATCH, wenn es sich ändern kann)
eine fachliche Regel prüfen und ablehnenbefore, mit einer Exception
die neue id weiterverwendenafter, CREATE
verhindern, dass etwas gelöscht wirdbefore, DELETE, mit einer Exception
einen Wert nur für die Antwort umrechnenafter, READ
einen Wert verschlüsselt speichern oder an vielen Feldern gleich behandelnFeld-Hook
eine E-Mail schicken, sobald die Änderung sicher gespeichert istkein Hook-Punkt liegt nach dem Commit, siehe Fallen

Fallen

Wie es weitergeht

Quellen im Code und in der Wissensdatenbank
  • commons – global/commons/interfaces/HookServiceInterface, HookFieldInterface, enumerations/CmsMethods
  • CDMS/cdms-system-layer – HookManagementSystem (callHooksBeforeDatabase, callHooksAfterDatabase, modelHooksOf: getBeanNamesForType, Sortierung nach @Order, ohne Spring-Profil); session/HookRequestContext (addHookBefore, addHookAfter, runAllBefore, runAllAfter)
  • CDMS/cdms-system-layer – AbstractSystemLayer und AbstractSystemSingletonLayer (createObject, updateObject, patchObject, deleteObject, historyRollback); AbstractLayer (recursiveCreate, recursiveUpdate, recursivePatch, recursiveDelete, recursiveRead, recursiveQuery)
  • CDMS/cdms-persistence-database – AbstractDatabasePersistence.queryObjects (Objekte aus den gelesenen Spalten gebaut)
  • CDMS/cdms-integrationtest – TestHookManagement, AbstractHookValidationTest, AbstractReadHookTest, AbstractSingletonHookValidationTest, AbstractRecursiveDelete
  • documentation/60-erweiterung/01-hooks.md
Suchen