Worum es geht
Jedes Mal, wenn CDMS ein Objekt liest oder schreibt, muss es wissen, in welche Datenbank der Zugriff geht. Diese Datenbank heißt Persistenzziel. Es gibt nur zwei Arten von Zielen:
- die System-Datenbank mit dem Schlüssel
system, - die Datenbank eines Mandanten mit dem Mandantenschlüssel, etwa
acme.
Die Entscheidung fällt für jedes Modell einzeln, nicht einmal pro Anfrage. Zwei Dinge bestimmen sie: die Ebene des Modells und der Mandant im RequestContext. Ein Header, ein Feld in den Daten oder ein Parameter der URL spielen keine Rolle.
Der Ablauf
flowchart TD
A(["Zugriff auf ein Modell"]) --> S{"System-Modell?"}
S -- ja --> SYS[("System-Datenbank")]
S -- nein --> K{"RequestContext vorhanden?"}
K -- nein --> E500["500 CDMS_PERSISTENCE_CONTEXT_MISSING"]
K -- ja --> T{"Mandant gesetzt?"}
T -- nein --> M1{"Betriebsart?"}
M1 -- SINGLE --> ONE[("die eine Datenbank")]
M1 -- MULTI --> E400["400 CDMS_TENANT_REQUIRED"]
T -- ja --> M2{"Betriebsart?"}
M2 -- SINGLE --> ONE
M2 -- MULTI --> R{"reservierter Schlüssel?<br/>system, single"}
R -- ja --> E403R["403 CDMS_TENANT_KEY_RESERVED"]
R -- nein --> L{"Mandant und Wechselziel<br/>in der Liste der erlaubten Mandanten?"}
L -- nein --> E403["403 CDMS_TENANT_SWITCH_NOT_AUTHORIZED"]
L -- ja --> TDB[("Datenbank des Mandanten")]
Die Reihenfolge ist Absicht: Die Frage „System-Modell?“ kommt vor jeder Prüfung des Kontexts. Ein fehlender oder falscher RequestContext kann ein System-Modell deshalb weder in eine andere Datenbank umleiten noch blockieren.
Die Entscheidungstabelle
| Ebene des Modells | RequestContext | Mandant | Betriebsart | in der Liste der erlaubten Mandanten | Persistenzziel |
|---|---|---|---|---|---|
| System | – | – | – | – | System-Datenbank |
| Mandant / Benutzer | fehlt | – | – | – | 500 CDMS_PERSISTENCE_CONTEXT_MISSING |
| Mandant / Benutzer | da | fehlt | SINGLE | – | die eine Datenbank |
| Mandant / Benutzer | da | fehlt | MULTI | – | 400 CDMS_TENANT_REQUIRED |
| Mandant / Benutzer | da | acme | SINGLE | – | die eine Datenbank; der Mandant zählt nicht |
| Mandant / Benutzer | da | acme | MULTI | ja | Datenbank acme |
| Mandant / Benutzer | da | acme | MULTI | nein | 403 CDMS_TENANT_SWITCH_NOT_AUTHORIZED |
| Mandant / Benutzer | da | system oder single | MULTI | – | 403 CDMS_TENANT_KEY_RESERVED, auch wenn der Schlüssel in der Liste steht |
Die Ebene eines Modells legst du im Hub fest, siehe Modell-Ebenen. Der Generator macht daraus die Basisklasse der Entity: AbstractSystemModel, AbstractTenantModel oder AbstractUserModel. Aus ihr liest CDMS zur Laufzeit ab, ob ein Modell ein System-Modell ist.
Die Fehlerfälle
Wann: Code greift auf ein Mandanten-Modell zu, ohne dass ein RequestContext existiert.
-
1CDMSsucht den RequestContext des aktuellen Threads und findet keinen
-
2CDMS500
CDMS_PERSISTENCE_CONTEXT_MISSING
Ergebnis: Das passiert nicht bei normalen Anfragen, sondern in eigenem Code: ein eigener Thread, ein zeitgesteuerter Job, ein Listener. Solcher Code muss sich den Kontext selbst holen, siehe Arbeiten für einen Mandanten ohne Anfrage.
Wann: MULTI, der RequestContext enthält keinen Mandanten.
-
1CDMSwill ein Mandanten-Modell lesen oder schreiben
-
2CDMS400
CDMS_TENANT_REQUIRED
Ergebnis: Normale Anfragen ohne Mandanten weist schon die Filterkette mit 403 ab, siehe Woher der Mandant einer Anfrage kommt. Diese Prüfung ist die zweite Sicherung dahinter.
Wann: MULTI, der Mandant oder das Ziel eines Wechselwunsches steht nicht in der Liste der erlaubten Mandanten.
-
1CDMSvergleicht Mandant und Wechselziel mit der Liste aus dem Token
-
2CDMS403
CDMS_TENANT_SWITCH_NOT_AUTHORIZED
Ergebnis: Ein abgelehnter Wechsel wird damit sichtbar, statt still im eigenen Mandanten weiterzulaufen. Siehe Mandantenwechsel per Header.
Vom Mandantenschlüssel zur Datenbank
Steht das Ziel fest, baut CDMS daraus die Verbindung. Die Datenbank-URL der Installation enthält den Platzhalter {tenant}:
CODAMAI_PERSISTENCE_DATABASE_URL=jdbc:mysql://db:3306/{tenant}
| Ziel | Datenbank |
|---|---|
System-Datenbank in MULTI | jdbc:mysql://db:3306/system |
Mandant acme in MULTI | jdbc:mysql://db:3306/acme |
alles in SINGLE | jdbc:mysql://db:3306/single |
Alle Datenbanken liegen also auf demselben Datenbankserver und nutzen dieselben Zugangsdaten. Getrennt sind sie durch den Datenbanknamen.
Gibt es die Datenbank noch nicht oder ist der Server nicht erreichbar, endet der Zugriff mit einem eigenen Fehler, siehe Datenbanken, Pools, Migration:
| Fehler | Status | Bedeutung |
|---|---|---|
CDMS_TENANT_DATASOURCE_NOT_FOUND | 500 | Die Datenbank des Mandanten fehlt, und automatisches Anlegen ist nicht freigegeben. |
CDMS_TENANT_DATASOURCE_UNAVAILABLE | 503 | Der Datenbankserver antwortet nicht. CDMS versucht dann nicht, etwas anzulegen. |
CDMS_ENTITY_MANAGER_CREATION_FAILED | 500 | Die Datenbank ist da, aber der Zugang dazu ließ sich nicht aufbauen. |
Eine Anfrage, zwei Ziele
Eine Anfrage kann beide Arten von Zielen berühren, etwa wenn ein Hook an einem Mandanten-Modell zusätzlich ein System-Modell schreibt. CDMS öffnet dann für jedes Ziel eine eigene Verbindung und eine eigene Transaktion. Was das für Fehler bedeutet, steht unter Keine Atomarität über zwei Datenbanken.
Fallen
Wie es weitergeht
- Woher der Mandant im RequestContext kommt: Woher der Mandant einer Anfrage kommt
- Welche Modelle auf welche Ebene gehören: Modell-Ebenen
- Die Regeln hinter dieser Tabelle: Regeln, die nie gebrochen werden