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.
-
FilterketteTokenIst die Signatur gültig und das Token nicht abgelaufen?↳ nein 401
-
CIASMandantWelcher Mandant, und wird er bedient?↳ nein 403 mit
cias.authentication.… -
CDMSFeldauswahlSteht eine
responseim Körper, und lässt sich der Körper lesen?↳ nein 400 -
CDMSSichtbarkeitIst die Zeile für diese Person überhaupt sichtbar – mit denselben Filtern wie beim Lesen?↳ nein 404, als gäbe es sie nicht
-
CDMSSchreibrolleErlaubt eine der effektiven Rollen das Ändern von
customer? Ebenso für jedes Kind, das mitgeschrieben wird↳ nein 403missing-permission|<rolle> -
CDMSWerte übertragenFelder aus
datains Objekt schreiben, Defaults setzen, Kinder anlegen oder verknüpfen. Regelverstöße werden nur gemerkt↳ nein 400 / 404 bei Kindern, die nicht auflösbar sind -
HookBefore-HooksDie 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
-
CDMSValidierungJeder gemerkte Verstoß wird gegen den Wert geprüft, der jetzt am Objekt steht↳ nein 422
validation-failedmit allen Verstößen auf einmal -
DatenbankSpeichern und flushDas SQL läuft, die Datenbank prüft ihre eigenen Regeln, etwa eindeutige Werte↳ nein 409
already-existsbei einem schon vergebenen Wert · 400constraint-violation, wenn ein Wert nicht in die Spalte passt · 503, wenn die Datenbank nicht erreichbar ist -
CDMSZurücklesenDas Objekt wird mit der
responseund den Leserechten der Person gelesen↳ nein 403 / 404 – und die Änderung wird mit zurückgenommen -
CDMSCommitTransaktion festschreiben, bevor die Antwort geschrieben wird↳ nein Rollback; 409 bei einer erkannten gleichzeitigen Änderung, sonst ein Serverfehler
- 200 mit dem gespeicherten Objekt – der Stand, der jetzt in der Datenbank steht
Warum diese Reihenfolge
| Reihenfolge | Warum sie so sein muss |
|---|---|
| Mandant vor Rolle | Welche Rollen gelten, hängt vom Mandanten ab. Die Rollen im aktiven Mandanten ersetzen die globalen, siehe Effektive Rollen. |
| Sichtbarkeit vor allem anderen | Sonst würde eine Fehlermeldung verraten, dass es eine fremde Zeile gibt. Es reicht nicht, eine id zu kennen. |
| Rolle vor Hooks | Fehlt 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 Validierung | Ein 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 Commit | Die Antwort soll den gespeicherten Stand zeigen. Scheitert das Lesen, ist auch das Schreiben hinfällig. |
| Commit vor der Antwort | Sonst 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 Stufe | Wer antwortet | Was in der Datenbank steht | Liefen Hooks? |
|---|---|---|---|
| Token, Mandant | Filterkette (CIAS) | nichts, es gab keine Transaktion | nein |
| Feldauswahl, Körper | REST-Layer | nichts | nein |
| Sichtbarkeit, Rolle | System-Layer | nichts; die Transaktion wurde höchstens zum Lesen geöffnet | nein |
| Werte übertragen | System-Layer | nichts | nein |
| Before-Hook lehnt ab | Hook | nichts in der Datenbank – außerhalb davon bleibt, was der Hook ausgelöst hat | ja, bis zu diesem Hook |
| Validierung | System-Layer | nichts | ja, alle Before-Hooks |
| flush (Datenbankregel) | Persistenz | nichts, alles wird zurückgerollt | ja, Before und After |
| Zurücklesen | System-Layer | nichts, die Änderung wird zurückgenommen | ja, alle |
| Commit | Persistenz | nichts | ja, 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.
| Alle Stufen bestanden | Commit gelingt | Ergebnis |
|---|---|---|
| ja | ja | 200 mit dem Objekt, die Änderung ist dauerhaft |
| ja | nein | Rollback; 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.
-
1CDMS→DatenbankAnfang: beim ersten Datenbankzugriff der Anfrage. Beim Ändern ist das die Sichtbarkeitsprüfung, beim Anlegen das erste SpeichernNicht schon beim Eintreffen der Anfrage. Eine Anfrage, die an Token, Mandant oder Feldauswahl scheitert, hat nie eine Transaktion geöffnet.
-
2CDMS→Datenbankflush: das gesammelte SQL geht an die Datenbank. Sie prüft ihre Regeln, andere Anfragen sehen davon noch nichts
-
3CDMS→DatenbankCommit: unmittelbar bevor der Antwortkörper geschrieben wirdErgebnis: 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.
- 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
- 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.
-
1Filterketteliest IP-Adresse und User-Agent aus der Anfrage und Benutzer-ID und Name aus dem Token, und legt sie in den RequestContext
-
2CDMS→Datenbankbeim flush schreibt Envers eine Zeile in
revinfound für jedes geänderte Objekt eine Zeile in dessen_AUD-TabelleDie vier Werte kommen aus dem RequestContext, nicht aus dem Objekt. Ohne Anfragekontext – etwa in einem Hintergrundjob – bleiben sie leer. -
3CDMS→Datenbankmit dem Commit wird die Revision dauerhaft, zusammen mit der ÄnderungErgebnis: 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.
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
- Vom Login bis zu den Daten: derselbe Weg beim Lesen
- Der Weg einer Anfrage durch die Schichten: die Stationen innerhalb von CDMS
- Die Reihenfolge in einem Schreibvorgang: wo genau die Hooks sitzen
- Validierung und Wenn ein Hook scheitert
- Ein Request, eine Transaktion
- Was auditiert wird