Zum Inhalt springen

Schichten und Request-Flow

Kurzantwort: Ein Request läuft durch fünf Stationen: Filterkette (CIAS)REST-APISystem-LayerAutorisierung/Persistenz/DateienDatenbank/Dateisystem. Die REST-Schicht kennt nur Payloads, der System-Layer kennt die Fachlogik, die Persistenz kennt die Technik. Die Transaktion umklammert den gesamten Request, nicht die einzelne Service-Methode.


SchichtModulVerantwortung
Filterkettecias-authenticationToken prüfen, RequestContext aufbauen, Kontextwechsel autorisieren
REST-APIcdms-rest-apiPayloads entgegennehmen, Multipart/Base64-Dateien in Temp-Dateien schreiben, Response Request expandieren, Fehler abbilden
System-Layercdms-system-layerRekursion über den Objektgraphen, Validierung, Hooks, Owner-/Attributfilter, Orchestrierung von Create+Read
Autorisierungcdms-authorizationRollenprüfung je Modell und Operation
Persistenzcdms-databasePersistenzziel auflösen, EntityManager, Criteria-Abfragen, Feldprojektion, Envers
Dateiencdms-localfs-storagePfad bilden, Bytes bewegen, Versionen halten

Die Dateischicht ist die einzige optionale: Ein System mit storage: NONE bekommt kein Backend und startet ohne eines. Siehe Dateien und Storage.

sequenceDiagram
    autonumber
    participant C as Client
    participant F as JwtSessionFilter (CIAS)
    participant A as {Model}Api (REST)
    participant E as Expander
    participant S as {Model}System (System-Layer)
    participant H as Hooks
    participant Z as Autorisierung
    participant P as {Model}Database
    participant DB as Datenbank

    C->>F: POST /api/rest/<pfad>/create + Bearer-Token
    F->>F: Token prüfen, RequestContext füllen,<br/>Tenant-/User-Switch autorisieren
    F->>A: Request weiterreichen
    A->>A: Multipart/Base64 → Temp-Dateien
    A->>E: Response Request expandieren
    E-->>A: RequestSingle (fields, models, lists)
    A->>S: createObject(DTO, request, files)
    S->>Z: createAccessAllowedByClass()
    S->>S: recursiveCreate: Felder setzen,<br/>Defaults, Validierung sammeln,<br/>Beziehungen rekursiv
    S->>H: Before-Hooks ausführen
    S->>S: assertValid() – alle Verstöße auf einmal
    S->>P: createObject(entity)
    P->>DB: INSERT
    S->>H: After-Hooks ausführen
    S->>P: flush()
    S->>S: readObject(id, request) – Antwort zusammenstellen
    S-->>A: DTO
    A-->>C: SingleResponse

Wichtig an der Reihenfolge: Hooks laufen vor der Validierung. Ein Hook darf ein Feld setzen, das der Client gar nicht kennt, und damit eine Pflichtregel erfüllen. Anschließend werden alle gesammelten Verstöße erneut geprüft und nur das gemeldet, was der Hook nicht behoben hat.

sequenceDiagram
    autonumber
    participant C as Client
    participant A as {Model}Api
    participant E as Expander
    participant S as System-Layer
    participant P as Persistenz
    participant DB as Datenbank

    C->>A: POST /read/{id} mit response-Definition
    A->>E: expandResponse(payload, meta)
    E->>E: '+' → skalare Felder, '*' → zusätzlich Referenzen (nur id),<br/>verschachtelte ModelResponse rekursiv
    E-->>A: RequestSingle
    A->>S: readObject(id, request)
    S->>S: readAccessAllowedByClass()
    S->>S: Sicherheitsfilter ergänzen (_userId, Attributfilter)
    S->>P: queryObjects(request) – Tuple-Projektion
    P->>DB: SELECT genau der angefragten Spalten
    DB-->>P: Tuple
    P-->>S: Entity (aus Tuple zusammengesetzt)
    S->>S: pro Referenz: recursiveRead / queryObjects
    S-->>A: DTO
    A-->>C: SingleResponse

Es werden nie ganze Objektgraphen geladen. Was nicht im Response Request steht, wird nicht aus der Datenbank geholt.

EntscheidungOrt
Darf der Benutzer diese Operation auf diesem Modell?cdms-authorization über die Modell-Rollen
Darf der Benutzer diese Zeile?System-Layer über Owner-Filter und Attributfilter
Welche Datenbank?Persistenz, anhand der Entity-Basisklasse
Welche Felder kommen zurück?REST (Expander) und Persistenz (Projektion)
Wann wird committet?Request-Ende, siehe Transaktionen
Was passiert bei fehlendem Feld im Payload?System-Layer, je nach Verb – siehe Schreibsemantik

Update, Patch, Delete und Rollback laden ihr Ziel über getFullEntity(id) – ein find, das keinen Filter anwendet. Damit die Kenntnis einer ID nicht genügt, um fremde Daten zu ändern, prüft der System-Layer vorher assertVisibleForWrite(id): dieselben Filter wie beim Lesen, per Count-Abfrage. Ist die Zeile unsichtbar, kommt 404 – nicht 403, denn „existiert, gehört dir aber nicht” würde die Existenz fremder Daten verraten.