CodamAIDocs
Themafertig

Modell-Ebenen: System, Mandant, Benutzer

Jedes Modell gehört zu einer von drei Ebenen. Die Ebene entscheidet, in welcher Datenbank die Daten liegen und ob jemand nur seine eigenen Zeilen sieht.

Ausprägungen
SYSTEM (immer System-DB)TENANT (Mandanten-DB)USER (Mandanten-DB + Besitzer _userId)Betriebsart MULTIBetriebsart SINGLEohne Angabe → TENANTUntertypen erben die EbeneBeziehungen über Ebenen hinwegtechnischer Client bei USER-Modellen

Worum es geht

Wenn du im Hub ein Modell anlegst, wählst du als Erstes seinen Scope, also die Ebene, auf der seine Daten leben. Es gibt genau drei:

Die drei Ebenen
System
Scope „Systemweit“, SYSTEM
  • gilt für die ganze Installation
  • liegt immer in der System-Datenbank
  • alle Mandanten sehen dieselben Daten
  • Beispiel: Länderliste, Produktkatalog der Plattform
Mandant
Scope „Mandant“, TENANT
  • gehört einem Kunden
  • liegt in der Datenbank dieses Mandanten
  • alle Personen des Mandanten sehen dieselben Daten (sofern ihre Rollen es erlauben)
  • Beispiel: Kunden, Aufträge, Audits einer Firma
Benutzer
Scope „Benutzer“, USER
  • gehört einer Person
  • liegt ebenfalls in der Mandanten-Datenbank
  • jede Person sieht nur ihre eigenen Zeilen
  • Beispiel: persönliche Einstellungen, Notizen, Favoriten

Wo die Daten liegen

In der Betriebsart MULTI hat jeder Mandant eine eigene Datenbank. Die Benutzerebene ist keine eigene Datenbank, sondern eine Verfeinerung innerhalb der Mandanten-Datenbank:

flowchart LR
    subgraph SYS["System-Datenbank"]
        S1["System-Modelle<br/>z. B. Länderliste"]
    end
    subgraph TA["Datenbank Mandant A"]
        A1["Mandanten-Modelle<br/>z. B. Kunden von A"]
        A2["Benutzer-Modelle<br/>Zeilen von Anna | Zeilen von Ben"]
    end
    subgraph TB2["Datenbank Mandant B"]
        B1["Mandanten-Modelle<br/>z. B. Kunden von B"]
        B2["Benutzer-Modelle<br/>Zeilen von Clara"]
    end
    SYS ~~~ TA ~~~ TB2

Benutzer-Modelle haben ein zusätzliches Feld _userId. CDMS trägt dort beim Anlegen die ID der angemeldeten Person ein und filtert beim Lesen danach.

Welche Ebene wähle ich?

Zwei Fragen genügen:

flowchart LR
    Q1{"Brauchen alle Kunden<br/>dieselben Daten?"}
    Q1 -->|ja| SYS["System"]
    Q1 -->|nein| Q2{"Gehören die Daten einer<br/>einzelnen Person, die sie<br/>als Einzige sehen darf?"}
    Q2 -->|ja| USR["Benutzer"]
    Q2 -->|nein| TEN["Mandant"]

Was bei einem Zugriff passiert

Die Ebene wirkt bei jedem Lesen und Schreiben an zwei Stellen: bei der Wahl der Datenbank und beim Filtern der Zeilen.

Ein Zugriff auf jeder Ebene

Wann: Das Modell hat den Scope „Systemweit“.

  1. 1
    Client→CDMS
    liest oder schreibt, mit oder ohne Mandant im Token
  2. 2
    CDMS
    erkennt am Modell: System-Ebene. Der Mandant wird gar nicht erst gelesen, auch ein tenant-Header ändert nichts
  3. 3
    CDMS→System DB
    greift auf die System-Datenbank zu, ohne Zeilenfilter der Ebene

Ergebnis: Alle sehen dieselben Daten, soweit ihre Rollen es erlauben.

Wann: Das Modell hat den Scope „Mandant“ (oder keinen).

  1. 1
    Client→CDMS
    liest oder schreibt, der Mandant steht im Token
  2. 2
    CDMS
    Gibt es einen Mandanten im Kontext? Darf diese Anfrage auf ihn zugreifen?
    Kein Mandant in MULTI → 400 CDMS_TENANT_REQUIRED. Mandant nicht erlaubt → 403.
  3. 3
    CDMS→Mandanten-DB
    greift auf die Datenbank genau dieses Mandanten zu

Ergebnis: Jede Person des Mandanten sieht dieselben Daten, soweit ihre Rollen es erlauben. Andere Mandanten sehen nichts davon.

Wann: Das Modell hat den Scope „Benutzer“.

  1. 1
    Client→CDMS
    liest oder schreibt, Mandant und Person stehen im Token
  2. 2
    CDMS
    wählt die Mandanten-Datenbank, genau wie bei der Ebene Mandant
  3. 3
    CDMS
    beim Anlegen: setzt _userId auf die ID der angemeldeten Person. Einen mitgeschickten Wert ignoriert CDMS
  4. 4
    CDMS
    beim Lesen, Suchen, Ändern, Löschen: hängt den Pflichtfilter _userId = angemeldete Person an
  5. 5
    CDMS→Mandanten-DB
    liefert oder ändert nur Zeilen dieser Person

Ergebnis: Jede Person sieht nur ihre eigenen Zeilen. Fremde Zeilen gibt es aus ihrer Sicht nicht: 404, nicht 403.

Welche Datenbank? Die vollständige Entscheidung

So entscheidet die Persistenz bei jedem einzelnen Zugriff. Die Reihenfolge ist wichtig: Die Frage „System-Modell?“ kommt vor allem anderen.

Persistenzziel je Zugriff
System-Modell?BetriebsartMandant im Kontext?Mandant steht in allowedTenants?Ziel
ja–––System-Datenbank
neinMULTIjajaDatenbank dieses Mandanten
neinMULTIjanein403 CDMS_TENANT_SWITCH_NOT_AUTHORIZED
neinMULTInein–400 CDMS_TENANT_REQUIRED
neinSINGLE––die eine gemeinsame Datenbank

„Erlaubt“ heißt: Der Mandant steht in der Liste allowedTenants aus dem Token. Fehlt die Liste, ist nichts erlaubt.

Die Tabelle zeigt die Sicht der Persistenz. Eine Anfrage ohne Mandant erreicht sie in MULTI meist gar nicht erst: Schon die Filterkette von CIAS lehnt sie mit 403 tenant-required ab. Die 400 ist die zweite Sicherung dahinter.

Einen Rückfall gibt es nicht: Fehlt der Mandant, landet ein Mandanten-Objekt nie stillschweigend in der System-Datenbank. Ob ein Mandant überhaupt bedient wird (aktiv, nicht gesperrt), hat vorher schon das Mandanten-Tor in CIAS entschieden.

SINGLE und MULTI

Die Ebenen gibt es in beiden Betriebsarten. Was sich ändert, ist nur die Datenhaltung:

MULTISINGLE
Datenbankeneine System-DB + eine DB je Mandantgenau eine
System-ModelleSystem-DBdie eine DB
Mandanten-ModelleDB des Mandantendie eine DB, ohne Trennung nach Kunden
Benutzer-ModelleDB des Mandanten, gefiltert nach _userIddie eine DB, gefiltert nach _userId
Mandant im Token nötig?ja, sonst 400nein

Beziehungen zwischen den Ebenen

Eine Beziehung verbindet zwei Modelle, und beide müssen in derselben Datenbank erreichbar sein. Daraus folgt die Regel, die der Hub beim Modellieren durchsetzt:

Darf Modell A auf Modell B verweisen?
Ebene von AEbene von BBeziehung
SystemSystemerlaubt – beide in der System-DB
MandantMandanterlaubt – beide in der Mandanten-DB
MandantBenutzererlaubt – beide in der Mandanten-DB
BenutzerMandanterlaubt – beide in der Mandanten-DB
Mandant oder BenutzerSystemnicht erlaubt – Ziel liegt in einer anderen Datenbank
SystemMandant oder Benutzernicht erlaubt – Ziel liegt in einer anderen Datenbank

Im Hub sind unzulässige Ziele im Auswahlfeld ausgegraut („Scope unzulässig“) und werden beim Speichern abgewiesen.

Wie die Ebene festgelegt wird

Vom Hub bis zur Klasse
  1. 1
    Admin
    wählt im Hub beim Modell das Feld Scope: „Systemweit“, „Mandant“ oder „Benutzer“
  2. 2
    CDMS
    speichert es als modelType = SYSTEM, TENANT oder USER. Fehlt der Wert, gilt TENANT
  3. 3
    CDMS
    Der Generator leitet die Entity von der passenden Basisklasse ab: AbstractSystemModel, AbstractTenantModel oder AbstractUserModel
    Ein Untertyp eines abstrakten Modells erbt die Basisklasse seines Obermodells und damit dessen Ebene.
  4. 4
    CDMS
    Beim Start prüft CDMS, dass jede Entity in der richtigen Gruppe steht. Passt etwas nicht, startet die Anwendung nicht

Zwei Sicherungen gegen Verwechslung

Dass ein Objekt in der falschen Datenbank landet, verhindern zwei voneinander unabhängige Mechanismen:

Wie ein Objekt in die richtige Datenbank kommt
  1. CDMS
    Routing
    Wählt die Datenbank anhand der Basisklasse. System-Modelle ignorieren den Mandanten vollständig.
    ↳ nein 400 / 403, nie Rückfall auf die System-DB
  2. CDMS
    Typmengen
    Jede Datenbankverbindung kennt nur die Modelle ihrer Ebene. Die System-DB kennt keine Mandanten-Modelle und umgekehrt.
    ↳ nein ein falsch geroutetes Objekt scheitert an Hibernate, statt falsch gespeichert zu werden
  3. CDMS
    Startprüfung
    Passen Routing und Typmengen zusammen?
    ↳ nein Anwendung startet nicht
  4. Objekt liegt in der Datenbank seiner Ebene

Was sonst noch an der Ebene hängt

ThemaSystemMandantBenutzer
Singletonein Objekt für die ganze Installationein Objekt je Mandantein Objekt je Person
DateienAblagepfad ohne MandantPfad mit MandantPfad mit Mandant
HistorieRevisionsprotokoll der System-DBRevisionsprotokoll der Mandanten-DBRevisionsprotokoll der Mandanten-DB
Zeilenfilterkeiner durch die Ebenekeiner durch die Ebene, der Mandant wirkt über die Datenbank_userId = angemeldete Person

Rollen, Attributfilter und eigene Filter wirken zusätzlich auf jeder Ebene.

Fallen

Quellen im Code und in der Wissensdatenbank
  • CDMS/cdms-persistence-database – AbstractSystemModel, AbstractTenantModel, AbstractUserModel, AbstractEntityModel
  • CDMS/cdms-persistence-database/docs/14-entity-classification-and-managed-types.md
  • commons-persistence – DatabaseRequestContext.resolveTenant
  • CDMS/cdms-system-layer – AbstractLayer (Owner-Filter, _userId beim Create)
  • CDMS/cdms-generator – EntityProcessor (modelType → Basisklasse), CdmsYamlLoader
  • CDMS/frontend – ModelFormFields.vue (Scope), itemKinds.ts isRelationScopeCompatible
  • documentation/10-cdms-grundlagen/02-modelle-und-metadaten.md, 30-daten-und-persistenz/01-mandantentrennung.md
Suchen