CodamAIDocs
Themafertig

Abstrakte Modelle und @type

Ein Oberbegriff mit mehreren konkreten Typen, etwa „Kunde“ mit Privat- und Firmenkunde. Hier steht, wie die Hub-API Anfragen an den richtigen Untertyp weiterleitet und wann der Client @type angeben muss.

Ausprägungen
anlegen mit @typeersetzen und ändern mit @typelesen und löschen: Typ über die idSuche in zwei Phasenparallele LeseschritteEndpunkte der Untertypen direktRechte und Filter der Untertypenunbekannter Typ

Worum es geht

Manchmal gibt es von einer Sache mehrere Arten, die vieles gemeinsam haben, aber nicht alles. Ein Kunde kann ein Privatkunde (mit Geburtsdatum) oder ein Firmenkunde (mit Handelsregisternummer) sein. Beide haben Name, Adresse und Aufträge.

Dafür gibt es abstrakte Modelle: ein Obermodell mit den gemeinsamen Feldern und mehrere konkrete Untermodelle, die davon erben.

classDiagram
    class kunde {
        <<abstrakt>>
        name
        adresse
        auftraege
    }
    class privatkunde {
        geburtsdatum
    }
    class firmenkunde {
        handelsregisternummer
    }
    kunde <|-- privatkunde
    kunde <|-- firmenkunde

Der Typ: @type

Jedes Objekt trägt seinen konkreten Typ. In der Datenbank steht er in der Spalte _MODELTYPE, im JSON heißt er @type. Der Wert ist der API-Pfad des Untermodells mit Punkten statt Schrägstrichen:

UntermodellAPI-Pfad@type
Privatkunde/crm/privatkundecrm.privatkunde
Firmenkunde/crm/firmenkundecrm.firmenkunde

Die Hub-API: ein Eingang, mehrere Ziele

Für das abstrakte Modell erzeugt der Generator eine Hub-API mit den üblichen Pfaden, hier /api/rest/crm/kunde/…. Sie macht selbst nichts, sondern leitet jede Anfrage an die API des richtigen Untertyps weiter. Jeder Untertyp hat außerdem seine eigene, normale API.

flowchart LR
    C(["Client"]) --> H["Hub-API<br/>/crm/kunde"]
    H -->|"@type = crm.privatkunde"| P["API<br/>/crm/privatkunde"]
    H -->|"@type = crm.firmenkunde"| F["API<br/>/crm/firmenkunde"]
    C -.->|direkt| P
    C -.->|direkt| F

Woher die Hub-API den Typ kennt, hängt von der Operation ab:

Wie findet die Hub-API den Untertyp?
OperationQuelle des Typs
POST /create@type im data des Clients
PUT /update/{id}@type im data des Clients
PATCH /update/{id}@type im data des Clients
POST /read/{id}, GET /read/{id}der Server liest den gespeicherten Typ zur id nach
DELETE /delete/{id}der Server liest den gespeicherten Typ zur id nach
POST /queryje Treffer der gespeicherte Typ, siehe Suche in zwei Phasen

Alle Operationen im Ablauf

Anfragen an die Hub-API

Wann: POST /crm/kunde/create

  1. 1
    Client→CDMS
    schickt { "data": { "@type": "crm.privatkunde", "name": "Anna Muster", "geburtsdatum": "1990-04-01" }, "response": ["+"] }
  2. 2
    CDMS
    liest @type und damit die Payload des Privatkunden, inklusive seiner eigenen Felder
    Ohne @type weiß CDMS nicht, welcher Untertyp gemeint ist: 400 missing-type-for-abstract-field|data.
  3. 3
    CDMS
    gibt die Anfrage an die API /crm/privatkunde weiter
  4. 4
    CDMS→Database
    legt das Objekt an, mit den Rechten, Regeln und Hooks des Privatkunden

Ergebnis: Antwort wie beim Untertyp, mit "@type": "crm.privatkunde".

Wann: /read/{id}, /delete/{id}

  1. 1
    Client→CDMS
    schickt nur die id, kein @type nötig
  2. 2
    CDMS→Database
    liest den gespeicherten Typ zur id nach
  3. 3
    CDMS
    gibt die Anfrage an die API dieses Untertyps weiter

Ergebnis: Beim Lesen enthält die Antwort @type, damit der Client die Art unterscheiden kann.

Wann: PUT und PATCH /update/{id}

Wie beim Anlegen muss @type in data stehen, auch bei PATCH. Es muss der Typ sein, den das Objekt schon hat: Ein Objekt wechselt nie seinen Typ.

Wann: /crm/privatkunde/…

Du kannst jeden Untertyp auch direkt über seine eigene API ansprechen. Dann brauchst du kein @type, und die Suche liefert nur Objekte dieses Untertyps. Die Hub-API brauchst du, wenn du alle Arten gemeinsam lesen oder suchen willst.

Suchen über alle Untertypen: zwei Phasen

Eine Suche an der Hub-API muss Objekte verschiedener Typen in einer Liste liefern, richtig sortiert und geblättert. Das geht in zwei Phasen:

sequenceDiagram
    participant C as Client
    participant H as Hub-API /crm/kunde
    participant DB as Datenbank
    participant P as API privatkunde
    participant F as API firmenkunde
    C->>H: POST /query (Filter, Sortierung, Seite)
    Note over H,DB: Phase 1: nur IDs und Typen
    H->>DB: SELECT id, _MODELTYPE ... WHERE ... ORDER BY ... LIMIT
    DB-->>H: [ (id1, privat), (id2, firma) ] + totalCount
    Note over H,F: Phase 2: jede Zeile über ihren Untertyp lesen
    par parallel
        H->>P: read id1
    and
        H->>F: read id2
    end
    P-->>H: Objekt 1
    F-->>H: Objekt 2
    H-->>C: data in der Reihenfolge von Phase 1 + meta
Was dabei wichtig ist
  1. 1
    CDMS
    Phase 1 filtert, sortiert und blättert auf dem Obermodell. Sie legt fest, welche Objekte in welcher Reihenfolge kommen, und liefert totalCount
  2. 2
    CDMS
    Phase 2 liest jede Zeile über die API ihres Untertyps, mit dessen Rechten und Filtern
    Die Leseschritte laufen parallel, standardmäßig bis zu 8 gleichzeitig (Einstellung cdms_hub_query_parallelism). Läuft in derselben Anfrage schon eine Schreibtransaktion, liest CDMS nacheinander.
  3. 3
    CDMS
    Jede Zeile bekommt ihre eigene Kopie der Feldauswahl. Ein + bedeutet deshalb für einen Privatkunden „alle Felder des Privatkunden“ und für einen Firmenkunden „alle Felder des Firmenkunden“
  4. 4
    CDMS→Client
    setzt die Liste in der Reihenfolge aus Phase 1 zusammen
    Ergebnis: Eine gemischte Liste, jedes Objekt mit seinem @type und seinen eigenen Feldern.

Rechte und Filter

Jeder Untertyp ist ein vollständiges Modell mit eigenen Rollen. Wer über die Hub-API liest, braucht deshalb die Leserechte des Untertyps, zu dem das Objekt gehört. Attributfilter des Obermodells gelten automatisch auch für alle Untertypen: Ein Untertyp ist nie ein Weg an den Regeln des Obermodells vorbei.

In der Datenbank

Das Obermodell hat eine eigene Tabelle mit den gemeinsamen Feldern und der Spalte _MODELTYPE. Jeder Untertyp hat eine eigene Tabelle mit seinen zusätzlichen Feldern, verbunden über dieselbe id.

Tabelle kundeTabelle privatkunde
id_MODELTYPEnameidgeburtsdatum
5a2b…crm.privatkundeAnna Muster5a2b…1990-04-01
7c1d…crm.firmenkundeMuster GmbH––

Die Firma steht mit ihren eigenen Feldern in der Tabelle firmenkunde.

Fallen

Quellen im Code und in der Wissensdatenbank
  • CDMS/cdms-rest-api – AbstractHubApi (getApiLayer, queryData, readRow, getModelType)
  • CDMS/cdms-generator – ApiHubProcessor, EntityProcessor (JOINED, _MODELTYPE, DiscriminatorValue), RestPayloadProcessor (JsonTypeInfo @type), CdmsYamlLoader (Attributfilter der Obermodelle)
  • documentation/30-daten-und-persistenz/04-vererbung-und-abstrakte-modelle.md
Suchen