Worum es geht
Ein Feld-Hook ist Logik für einen einzelnen Wert. Er gehört nicht zu einem Modell, sondern zu einer Art von Wert. Deshalb kannst du denselben Feld-Hook an viele Felder in vielen Modellen hängen. Typische Fälle:
- einen Text verschlüsselt speichern und entschlüsselt ausliefern,
- einen Wert ausrechnen, etwa
summe = preis × menge, - einen Wert vereinheitlichen, etwa eine Telefonnummer in eine feste Schreibweise bringen.
Der Modell-Hook sieht das ganze Objekt und gehört zu genau einem Modell. Der Feld-Hook bekommt den Wert und gibt den Wert zurück, der stattdessen gilt.
Einen Feld-Hook schreiben
Ein Feld-Hook implementiert HookFieldInterface<T>. T ist der Java-Typ des Feldes, für einen Text also String:
@Component
public class EncryptHook implements HookFieldInterface<String> {
@Override
public String beforeDatabaseChange(String value, FieldHookContext context) {
return value == null ? null : cipher.encrypt(value);
}
@Override
public String afterDatabaseRead(String value, FieldHookContext context) {
return value == null ? null : cipher.decrypt(value);
}
}
Beide Methoden geben einen Wert zurück. Den setzt CDMS in das Feld. Willst du nichts ändern, gibst du value unverändert zurück.
Der zweite Parameter, der Kontext (FieldHookContext), sagt dir, wo du bist:
| Methode | liefert |
|---|---|
item() | das Objekt mit allen anderen Feldern |
modelMeta() | die Beschreibung des Modells |
field(), fieldName() | die Beschreibung und den Namen des Feldes |
method() | die Operation: CREATE, UPDATE, PATCH oder READ |
Ist die Klasse eine Spring-Bean, etwa mit @Component, nimmt CDMS diese Bean. Sonst legt CDMS die Klasse selbst über Spring an. In beiden Fällen bekommt sie ihre Abhängigkeiten per @Autowired oder @Value, zum Beispiel den Schlüssel.
Den Feld-Hook einem Feld zuordnen
Die Zuordnung steht im Modell, nicht im Code. Im Hub öffnest du das Feld und setzt im Reiter Regeln die Regel Feld-Hook. Als Wert trägst du den vollständigen Klassennamen ein:
com.example.shop.hooks.EncryptHook
Mehrere Hooks trennst du durch Komma. Sie laufen in dieser Reihenfolge:
com.example.shop.hooks.CompressHook, com.example.shop.hooks.EncryptHook
-
1Modellierersetzt am Feld die Regel Feld-Hook (
@hook) mit dem Klassennamen -
2Generatorprüft den Namen und schreibt die Klasse in die Beschreibung des FeldesEin Name ohne Paket, etwa
EncryptHook, bricht die Generierung ab. Eine Klasse, die es im Projekt nicht gibt, bricht das Kompilieren ab. -
3CDMSlegt beim Start jeden Feld-Hook an und prüft, ob sein Typ zum Feld passtPasst er nicht, startet die Anwendung nicht. Die Meldung nennt Modell, Feld und Hook:
field-hook-type-mismatch|Kunde.telefon -> …EncryptHook. -
4Feld-Hookläuft ab jetzt bei jedem Schreiben und Lesen dieses Feldes
Einen Feld-Hook gibt es nur an einfachen Feldern, also Text-, Zahlen-, Datums- und Enum-Feldern. Eine Beziehung bekommt keinen Feld-Hook. Dafür ist der Modell-Hook da.
Der Weg eines Wertes
sequenceDiagram
participant C as Client
participant D as CDMS
participant H as Feld-Hook
participant DB as Datenbank
C->>D: POST code = "geheim"
D->>D: Before-Hooks des Modells
D->>H: beforeDatabaseChange("geheim")
H-->>D: "x7Kq…"
D->>DB: speichert "x7Kq…"
D->>DB: liest das Objekt für die Antwort
D->>H: afterDatabaseRead("x7Kq…")
H-->>D: "geheim"
D->>D: Lese-Hook des Modells
D-->>C: 200, code = "geheim"
Nach dem Schreiben läuft der Feld-Hook nicht. Die Antwort auf einen Schreibvorgang entsteht beim Lesen, und dort läuft afterDatabaseRead.
Wann der Hook vor dem Schreiben läuft
Vor dem Schreiben läuft ein Feld-Hook nur für Felder, die der Vorgang schreibt. So wird ein gespeicherter Wert nie ein zweites Mal verschlüsselt.
| Vorgang | Läuft beforeDatabaseChange? |
|---|---|
| Anlegen (POST) | ja, für jedes Feld mit Hook |
| Ersetzen (PUT) | ja, für jedes Feld mit Hook |
| Ändern (PATCH) | nur für die Felder, die der PATCH sendet |
| PUT oder PATCH, den eine Feldrolle abweist und der gespeicherte Wert bleibt | nein, der gespeicherte Wert bleibt, wie er ist |
| ein Before-Hook des Modells ändert das Feld | ja, auch dieser Wert läuft durch den Feld-Hook |
| Löschen | nein |
| Zurücksetzen auf einen alten Stand | nein, der alte Stand wird so übernommen, wie er gespeichert war |
Der Feld-Hook läuft nach den Before-Hooks des Modells und vor der Prüfung der Regeln. Ein Hook, der einen Wert ausrechnet, kann damit auch ein Pflichtfeld füllen. Hat ein Feld mehrere Hooks, laufen sie in der Reihenfolge aus dem Modell. Beim Lesen laufen sie in umgekehrter Reihenfolge: Was zuletzt angewendet wurde, wird zuerst zurückgenommen.
Ein Wert, der sich aus anderen ergibt
Ein Hook, der einen Wert ausrechnet, muss auch laufen, wenn nur eine seiner Quellen geändert wird. Ein PATCH mit nur preis sendet summe nicht mit. Deshalb meldet so ein Hook sich für jeden Schreibvorgang an:
@Component
public class TotalHook implements HookFieldInterface<Integer> {
@Override
public Integer beforeDatabaseChange(Integer value, FieldHookContext context) {
OrderEntity order = (OrderEntity) context.item();
if (order.getPrice() == null || order.getQuantity() == null)
return null;
return order.getPrice() * order.getQuantity();
}
@Override
public Integer afterDatabaseRead(Integer value, FieldHookContext context) {
return value;
}
@Override
public boolean runsOnEveryWrite() {
return true;
}
}
Mit runsOnEveryWrite() läuft der Hook bei jedem Anlegen, Ersetzen und Ändern des Objekts. Er bekommt dann auch den gespeicherten Wert und ignoriert ihn. Beim Löschen und beim Zurücksetzen läuft er nicht.
Feldrollen und Feld-Hooks
Eine Feldrolle verlangt ihre Rolle nur für eine Änderung. Schickst du einen Datensatz so zurück, wie du ihn gelesen hast, brauchst du keine. CDMS vergleicht dafür den gesendeten Wert mit dem gespeicherten, und zwar so, wie ein Leser ihn sieht, also nach afterDatabaseRead. Ein verschlüsseltes Feld, das du unverändert zurückschickst, gilt deshalb nicht als geändert.
Entscheidungstabelle
| Was du tun willst | Hook |
|---|---|
| einen einzelnen Wert umwandeln, etwa verschlüsseln | Feld-Hook |
| einen Wert aus anderen Feldern desselben Objekts ausrechnen | Feld-Hook mit runsOnEveryWrite() |
| dieselbe Logik an vielen Feldern in vielen Modellen | Feld-Hook |
| eine Regel prüfen, die mehrere Felder zusammen betrifft, und ablehnen | Modell-Hook |
| Beziehungen pflegen oder andere Datensätze schreiben | Modell-Hook |
| etwas an einer Beziehung tun | Modell-Hook, Feld-Hooks gibt es nur an einfachen Feldern |
Fallen
Wie es weitergeht
- Der mitgelieferte Feld-Hook zum Verschlüsseln: Verschlüsselte Felder
- Der Hook für das ganze Objekt: Hooks: Arten und Zeitpunkte
- Wo die Hooks zwischen Rollenprüfung, Validierung und Speichern liegen: Die Reihenfolge in einem Schreibvorgang
- Was der Client sieht, wenn ein Hook wirft: Wenn ein Hook scheitert