Hooks – eigene Businesslogik einhängen
Kurzantwort: Hooks sind der vorgesehene Erweiterungspunkt. Ein Hook ist eine
Spring-Bean, die HookServiceInterface<T> für eine Entity implementiert und
@Order trägt. CDMS ruft sie vor und nach dem Datenbankzugriff auf –
und zwar vor der Validierung, damit ein Hook Pflichtfelder noch füllen kann.
Die Hook-Arten
Abschnitt betitelt „Die Hook-Arten“| Ebene | Interface | Methoden |
|---|---|---|
| Klasse | HookServiceInterface<T extends ModelInterface> | beforeDatabaseChange, afterDatabaseChange, fieldBeforeDatabaseChange, fieldOnDatabaseRead |
| Feld | HookFieldInterface | dieselben vier, aber je Feld registriert über @Hook({...}) |
Der Methodenparameter CmsMethods sagt, welche Operation läuft:
CREATE, READ, UPDATE, PATCH, DELETE, QUERY, ROLLBACK.
Beispiel
Abschnitt betitelt „Beispiel“@Order(100)@Servicepublic class ModelFieldHook implements HookServiceInterface<ModelFieldEntity> {
@Override public void beforeDatabaseChange(ModelFieldEntity item, CmsMethods method) { if (method == CmsMethods.CREATE || method == CmsMethods.UPDATE) { // Gegenseite einer Beziehung synchron halten, Feldnamen eindeutig erzwingen } }
@Override public void afterDatabaseChange(ModelFieldEntity item, CmsMethods method) { }
@Override public void fieldBeforeDatabaseChange(ModelFieldEntity item, String field, CmsMethods method) { }
@Override public void fieldOnDatabaseRead(ModelFieldEntity item, String field, CmsMethods method) { }}@Order ist Pflicht – die Hook-Verwaltung sortiert danach und greift beim
Sortieren direkt auf die Annotation zu.
Wann sie laufen
Abschnitt betitelt „Wann sie laufen“sequenceDiagram
participant S as System-Layer
participant R as Rekursion
participant H as Hooks
participant V as Validierung
participant P as Persistenz
S->>R: recursiveCreate / Update / Patch
R->>R: Felder setzen, Beziehungen auflösen,<br/>Regelverstöße sammeln
R->>H: Hooks registrieren (before/after je Objekt)
S->>H: runAllBefore – alle Before-Hooks
Note over H: darf Felder setzen, die der Client nicht kennt
S->>V: assertValid – Verstöße erneut prüfen
V-->>S: nur noch echte Verstöße → 422
S->>P: persist / merge
S->>H: runAllAfter – alle After-Hooks
S->>P: flush
Die Reihenfolge Hooks vor Validierung ist bewusst: Ein Before-Hook darf ein Feld ableiten, das der Client weder kennt noch schickt, und damit eine Pflichtregel erfüllen. Anschließend wird jeder gesammelte Verstoß erneut gegen die Entity geprüft – ein Hook, der einen fehlenden Wert durch einen unzulässigen ersetzt, wird von der Regel gefasst, die er jetzt bricht.
Registrierung und Ausführung
Abschnitt betitelt „Registrierung und Ausführung“Die Rekursion registriert Hooks (addHooksBeforeDatabase /
addHooksAfterDatabase) pro besuchtem Objekt im request-scoped
HookRequestContext. Ausgeführt werden sie gesammelt über runAllBefore(...)
bzw. runAllAfter(...) im AbstractSystemLayer.
Vorteil: Jedes Objekt der Rekursion – auch jedes kaskadierte Kind – registriert seine eigenen Hooks über seinen eigenen System-Layer. Ein einzelner Auslöser deckt damit den ganzen Baum ab.
Die Zuordnung Entity → Hook-Beans wird beim ersten Zugriff aufgelöst und statisch gecacht (Klassen- und Feldebene getrennt).
Hooks beim Löschen
Abschnitt betitelt „Hooks beim Löschen“recursiveDelete registriert die DELETE-Hooks für das Objekt selbst und für
jedes kaskadierte Kind, jeweils über dessen eigenen System-Layer. Ausgeführt
werden sie über dieselben Dispatcher wie beim Schreiben.
Hooks beim Lesen
Abschnitt betitelt „Hooks beim Lesen“Die Persistenz ruft am Ende von readObject(...) genau einen Hook-Punkt:
callHooksAfterDatabase(entity, CmsMethods.READ). Alle übrigen Punkte setzt der
System-Layer.
Fehler aus Hooks
Abschnitt betitelt „Fehler aus Hooks“| Wurf | Bedeutung |
|---|---|
HookValidationException | fachliche Ablehnung → 422 |
HookExecutionException | technischer Fehler → 500 |
Beide sind CodamAI-Exceptions und lösen über den zentralen Exception-Mapper das Rollback des gesamten Requests aus.
- Generierter Code wird nicht angefasst. Fachlogik gehört in Hooks und
eigene Klassen unter
src/main/java. - Owning side beachten. Wer in einem Hook Beziehungen ändert, muss beide Seiten pflegen – siehe Beziehungen.
- Keine Filter umgehen. Wer im Hook direkt auf die Persistenz zugreift, umgeht Owner- und Attributfilter.
- Hooks sind synchron und Teil der Transaktion. Langlaufendes gehört nicht hinein.
- Keine Businesslogik in die Persistenz. Das Persistenzmodul definiert selbst keine Hooks – es ruft nur.
Beispiel aus dem Bestand
Abschnitt betitelt „Beispiel aus dem Bestand“Ein ModelFieldHook implementiert HookServiceInterface<ModelFieldEntity>: Er
hält bei Beziehungsfeldern die Gegenseite synchron, erzwingt eindeutige
Feldnamen und räumt beim Löschen die Spiegelbeziehung ab. Nach demselben Muster
arbeiten Hooks für Enumerationen, Enum-Werte, Ordner und Primitivfelder.