CodamAIDocs
Themafertig

Ein Schreibvorgang über alle Schichten

Ein Formular wird gespeichert: vom Klick über BFF, Token-Prüfung, Rechte, Validierung, Hooks, Speichern, Audit und Commit bis zur Antwort.

Ausprägungen
Anlegen (POST /create)Ändern (PUT und PATCH)Löschen (DELETE)Ablehnung in jeder StufeErfolg: Commit vor der AntwortFehler: Rollback der ganzen Anfrage

Worum es geht

Beim Lesen entscheidet sich nur, was zurückgeht. Beim Schreiben entscheidet sich, was in der Datenbank steht – und dafür laufen deutlich mehr Stationen, in einer festen Reihenfolge. Diese Seite spielt einen Schreibvorgang von der Schaltfläche „Speichern“ bis zum Commit durch und sagt an jeder Stelle, wer gerade prüft und was bei einer Ablehnung passiert.

Der Leseweg steht daneben unter Vom Login bis zu den Daten. Die erste Hälfte ist dieselbe; diese Seite beginnt dort, wo sie sich trennen.

Der ganze Weg in einem Bild

sequenceDiagram
    autonumber
    participant U as Benutzer
    participant F as BFF
    participant FK as Filterkette (CIAS)
    participant R as REST-Layer (CDMS)
    participant S as System-Layer (CDMS)
    participant H as Hook
    participant DB as Datenbank
    U->>F: klickt „Speichern“
    F->>F: Cookie entschlüsseln, Access-Token holen (ggf. erneuern)
    F->>FK: PUT /api/rest/crm/customer/update/42<br/>Bearer-Token, data + response
    FK->>FK: Token prüfen und tauschen, Mandant auflösen,<br/>Mandanten-Tor, Rollen und Attribute
    FK->>R: RequestContext gefüllt
    R->>R: Körper lesen, response auflösen, Dateien übernehmen
    R->>S: updateObject(DTO)
    S->>DB: ist Zeile 42 für diese Person sichtbar? Objekt laden
    Note over S,DB: erster Datenbankzugriff: hier beginnt die Transaktion
    S->>S: Rekursion über Objekt und Kinder:<br/>Rolle je Objekt prüfen, Werte übertragen,<br/>Regelverstöße merken, Hooks vormerken
    S->>H: Before-Hooks
    H-->>S: dürfen Felder noch ändern
    S->>S: Validierung: gemerkte Verstöße erneut prüfen
    S->>DB: Änderung an die Datenbank übergeben
    S->>H: After-Hooks
    S->>DB: flush: SQL wird ausgeführt, Datenbankregeln greifen,<br/>Envers schreibt die Revision
    S->>DB: mit der response zurücklesen (READ-Hook je Objekt)
    S-->>R: DTO
    R->>DB: COMMIT
    R-->>F: 200 mit data und meta
    F-->>U: Formular zeigt den gespeicherten Stand

Bis einschließlich „RequestContext gefüllt“ ist das derselbe Weg wie beim Lesen. Ab dem REST-Layer wird es ein anderer.

Wer prüft wann

Die Reihenfolge ist keine Geschmacksfrage: Jede Stufe setzt voraus, dass die vorige bestanden ist.

PUT /api/rest/crm/customer/update/42
  1. Filterkette
    Token
    Ist die Signatur gültig und das Token nicht abgelaufen?
    ↳ nein 401
  2. CIAS
    Mandant
    Welcher Mandant, und wird er bedient?
    ↳ nein 403 mit cias.authentication.…
  3. CDMS
    Feldauswahl
    Steht eine response im Körper, und lässt sich der Körper lesen?
    ↳ nein 400
  4. CDMS
    Sichtbarkeit
    Ist die Zeile für diese Person überhaupt sichtbar – mit denselben Filtern wie beim Lesen?
    ↳ nein 404, als gäbe es sie nicht
  5. CDMS
    Schreibrolle
    Erlaubt eine der effektiven Rollen das Ändern von customer? Ebenso für jedes Kind, das mitgeschrieben wird
    ↳ nein 403 missing-permission|<rolle>
  6. CDMS
    Werte übertragen
    Felder aus data ins Objekt schreiben, Defaults setzen, Kinder anlegen oder verknüpfen. Regelverstöße werden nur gemerkt
    ↳ nein 400 / 404 bei Kindern, die nicht auflösbar sind
  7. Hook
    Before-Hooks
    Die eigene Fachlogik des Projekts sieht das fertig befüllte Objekt und darf es noch ändern
    ↳ nein Der Hook kann selbst ablehnen, mit seiner eigenen Antwort
  8. CDMS
    Validierung
    Jeder gemerkte Verstoß wird gegen den Wert geprüft, der jetzt am Objekt steht
    ↳ nein 422 validation-failed mit allen Verstößen auf einmal
  9. Datenbank
    Speichern und flush
    Das SQL läuft, die Datenbank prüft ihre eigenen Regeln, etwa eindeutige Werte
    ↳ nein 409 already-exists bei einem schon vergebenen Wert · 400 constraint-violation, wenn ein Wert nicht in die Spalte passt · 503, wenn die Datenbank nicht erreichbar ist
  10. CDMS
    Zurücklesen
    Das Objekt wird mit der response und den Leserechten der Person gelesen
    ↳ nein 403 / 404 – und die Änderung wird mit zurückgenommen
  11. CDMS
    Commit
    Transaktion festschreiben, bevor die Antwort geschrieben wird
    ↳ nein Rollback; 409 bei einer erkannten gleichzeitigen Änderung, sonst ein Serverfehler
  12. 200 mit dem gespeicherten Objekt – der Stand, der jetzt in der Datenbank steht

Warum diese Reihenfolge

ReihenfolgeWarum sie so sein muss
Mandant vor RolleWelche Rollen gelten, hängt vom Mandanten ab. Die Rollen im aktiven Mandanten ersetzen die globalen, siehe Effektive Rollen.
Sichtbarkeit vor allem anderenSonst würde eine Fehlermeldung verraten, dass es eine fremde Zeile gibt. Es reicht nicht, eine id zu kennen.
Rolle vor HooksFehlt eine Rolle, läuft kein einziger Hook. Ein Hook kann also nichts auslösen, was die Person gar nicht gedurft hätte.
Hooks vor der ValidierungEin Before-Hook darf ein Pflichtfeld füllen, das der Client nicht kennt – etwa eine laufende Nummer. Siehe Die Reihenfolge in einem Schreibvorgang.
Zurücklesen vor dem CommitDie Antwort soll den gespeicherten Stand zeigen. Scheitert das Lesen, ist auch das Schreiben hinfällig.
Commit vor der AntwortSonst bekäme der Client ein „200“ für etwas, das danach noch scheitern kann.

Was bei Ablehnung in welcher Stufe passiert

Nicht jede Ablehnung hinterlässt denselben Zustand. Entscheidend ist, ob die Anfrage schon in der Datenbank war.

Abgelehnt in StufeWer antwortetWas in der Datenbank stehtLiefen Hooks?
Token, MandantFilterkette (CIAS)nichts, es gab keine Transaktionnein
Feldauswahl, KörperREST-Layernichtsnein
Sichtbarkeit, RolleSystem-Layernichts; die Transaktion wurde höchstens zum Lesen geöffnetnein
Werte übertragenSystem-Layernichtsnein
Before-Hook lehnt abHooknichts in der Datenbank – außerhalb davon bleibt, was der Hook ausgelöst hatja, bis zu diesem Hook
ValidierungSystem-Layernichtsja, alle Before-Hooks
flush (Datenbankregel)Persistenznichts, alles wird zurückgerolltja, Before und After
ZurücklesenSystem-Layernichts, die Änderung wird zurückgenommenja, alle
CommitPersistenznichtsja, alle

Zwei Sätze dazu, die im Alltag oft fehlen:

  • Ab der Validierung abwärts sind deine Before-Hooks schon gelaufen. Alles, was ein Hook außerhalb der Datenbank tut – eine Mail, ein Aufruf in ein anderes System – bleibt bestehen, auch wenn die Anfrage danach mit einem Fehler endet.
  • After-Hooks sehen noch keinen gesicherten Stand. Nach ihnen kommen noch flush, Zurücklesen und Commit. Jeder dieser drei Schritte kann die ganze Anfrage kippen.
Was am Ende in der Datenbank steht
Alle Stufen bestandenCommit gelingtErgebnis
jaja200 mit dem Objekt, die Änderung ist dauerhaft
janeinRollback; 409 bei einer erkannten gleichzeitigen Änderung, sonst ein Serverfehler
nein–Fehlerantwort, alles zurückgerollt – auch die Teile, die vorher schon geschrieben waren

Die Transaktionsgrenze

Eine Transaktion ist die Klammer um die Änderungen: entweder alle oder keine. In CDMS ist diese Klammer genau eine Anfrage.

Wo die Klammer auf- und zugeht
  1. 1
    CDMS→Datenbank
    Anfang: beim ersten Datenbankzugriff der Anfrage. Beim Ändern ist das die Sichtbarkeitsprüfung, beim Anlegen das erste Speichern
    Nicht schon beim Eintreffen der Anfrage. Eine Anfrage, die an Token, Mandant oder Feldauswahl scheitert, hat nie eine Transaktion geöffnet.
  2. 2
    CDMS→Datenbank
    flush: das gesammelte SQL geht an die Datenbank. Sie prüft ihre Regeln, andere Anfragen sehen davon noch nichts
  3. 3
    CDMS→Datenbank
    Commit: unmittelbar bevor der Antwortkörper geschrieben wird
    Ergebnis: Kommt die Erfolgsantwort beim Client an, sieht eine direkt folgende Anfrage die Änderung schon.

Scheitert irgendetwas, wird die ganze Anfrage als gescheitert markiert: Jede offene Transaktion wird sofort zurückgerollt, und am Ende der Anfrage wird nichts mehr festgeschrieben. Das gilt auch für Datenbanken, die schon vorher berührt wurden.

Was in der Klammer liegt
In der Transaktion
gelingt oder verschwindet gemeinsam
  • das Objekt selbst
  • alle verschachtelt angelegten, geänderten oder gelöschten Kinder
  • was Hooks über CDMS auf Modellen derselben Ebene schreiben
  • die Revision des Audits
  • das Zurücklesen für die Antwort
Außerhalb
bleibt auch nach einem Rollback
  • Dateiinhalte im Dateispeicher
  • Mails, HTTP-Aufrufe und Nachrichten aus einem Hook
  • alles, was eine zweite Datenbank betrifft
  • alles aus einer früheren Anfrage

Eine Anfrage, die ein System-Modell und ein Mandanten-Modell schreibt, hat zwei Transaktionen in zwei Datenbanken. Die werden nacheinander festgeschrieben, nicht gemeinsam. Siehe Keine Atomarität über zwei Datenbanken und Ein Request, eine Transaktion.

Audit und Revision

Ist das Modell auditiert, entsteht beim Schreiben eine Revision: ein Abzug des Objekts nach der Änderung, zusammen mit der Angabe, wer sie wann von wo gemacht hat.

Vom Request zur Revision
  1. 1
    Filterkette
    liest IP-Adresse und User-Agent aus der Anfrage und Benutzer-ID und Name aus dem Token, und legt sie in den RequestContext
  2. 2
    CDMS→Datenbank
    beim flush schreibt Envers eine Zeile in revinfo und für jedes geänderte Objekt eine Zeile in dessen _AUD-Tabelle
    Die vier Werte kommen aus dem RequestContext, nicht aus dem Objekt. Ohne Anfragekontext – etwa in einem Hintergrundjob – bleiben sie leer.
  3. 3
    CDMS→Datenbank
    mit dem Commit wird die Revision dauerhaft, zusammen mit der Änderung
    Ergebnis: Scheitert die Anfrage, verschwindet die Revision mit allem anderen. Es gibt keine Revision ohne Änderung.

Alle Änderungen einer Anfrage bekommen dieselbe Revisionsnummer – je Datenbank eine eigene. Was drinsteht und wie man sie liest: Was eine Revision festhält und Historie lesen.

CIAS führt daneben eine eigene Spur, für Rollenvergaben, Sperren und Mandantenänderungen. Die beiden Spuren haben nichts miteinander zu tun, siehe CIAS-Audit und CDMS-Historie.

Die drei Schreibarten im Vergleich

Der Weg ist für alle drei derselbe. Unterschiedlich ist, was er unterwegs prüft.

Anlegen, Ändern, Löschen

Wann: POST /create – ein neues Objekt, ohne id.

Keine Sichtbarkeitsprüfung, denn es gibt noch nichts zu sehen. Geprüft wird die Anlege-Rolle, und zwar auch für jedes Kind, das mitentsteht. Die Validierung sieht jedes Feld des Modells; ein fehlendes Feld gilt als leer. Defaultwerte sind zu diesem Zeitpunkt schon eingesetzt.

Ergebnis: 200 mit dem neuen Objekt und seiner id, im Audit eine Revision der Art ADD. Siehe Ein Objekt anlegen.

Wann: PUT /update/{id} oder PATCH /update/{id}.

Zuerst die Sichtbarkeit, dann wird das gespeicherte Objekt geladen, dann die Änderungs-Rolle. PUT beschreibt den ganzen Zielzustand und prüft deshalb jedes Feld; PATCH nennt nur die Änderung und prüft nur die gesendeten Felder. Ein Kind ohne id in einem PATCH wird angelegt – und mit den Regeln fürs Anlegen geprüft.

Ergebnis: 200 mit dem neuen Stand, im Audit eine Revision der Art MOD. Siehe PUT oder PATCH? Die null-Falle.

Wann: DELETE /delete/{id}.

Sichtbarkeit, dann die Lösch-Rolle je Objekt, dann werden abhängige Kinder zum Entfernen vorgemerkt und andere Beziehungen gelöst. Es gibt keine Validierung, kein Zurücklesen und keinen READ-Hook – es kommt ja nichts zurück.

Ergebnis: 200 ohne Körper, im Audit eine Revision der Art DEL mit nur noch der id. Siehe Der Ablauf eines DELETE.

Und in der getrennten Betriebsart?

Genau gleich. Der Unterschied zwischen „CIAS eingebettet“ und „CIAS als eigener Dienst“ liegt vollständig vor dem REST-Layer: Das Mandanten-Tor und der Attribut-Lookup stellen ihre Frage einmal per Methodenaufruf und einmal per HTTP. Ab dem RequestContext ist der Schreibweg derselbe Code mit denselben Prüfungen.

Die beiden Wege nebeneinander stehen unter Vom Login bis zu den Daten, alle Unterschiede unter Eingebettet und getrennt im Vergleich.

Fallen

Wie es weitergeht

Quellen im Code und in der Wissensdatenbank
  • hub-frontend – server/utils/backendFetch.ts, useCmsApi.ts, server/api/hub/projects/[projectId].put.ts
  • CIAS/cias-authentication – JwtSessionFilter (RequestContext mit IP und User-Agent), TokenParser.admit, TenantGate, EffectiveRoles, EffectiveAttributes
  • CDMS/cdms-rest-api – AbstractRestApi.createObject/updateObject/patchObject (getFileMap), RequestTransactionCommitter (beforeBodyWrite, postHandle, settle), RequestFailureMarker (markRollbackOnly, settle), CdmsExceptionMapper (markRollbackOnly, settle), payloads/WritePayload
  • CDMS/cdms-system-layer – AbstractSystemLayer.createObject/updateObject/patchObject/deleteObject (beginValidation, recursive*, runAllBefore, assertValid, persistence.*, runAllAfter, flush, readObject); AbstractLayer.recursivePrepare (Rollenprüfung je Objekt), assertVisibleForWrite; session/ValidationRequestContext, session/HookRequestContext
  • CDMS/cdms-authorization – AbstractAuthorizationLayer (createAccessAllowedByClass, updateAccessAllowedByClass, deleteAccessAllowedByClass)
  • CDMS/cdms-persistence-database – AbstractDatabasePersistence, auditing/AuditRevisionEntity, AuditRevisionListener (Werte aus dem RequestContext)
  • commons-persistence – DatabaseRequestContext (getEntityManager, resolveTenant, commitThreadTransactions, markRollbackOnly), TenantEntityManagerFactory
Suchen