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:
| Untermodell | API-Pfad | @type |
|---|---|---|
| Privatkunde | /crm/privatkunde | crm.privatkunde |
| Firmenkunde | /crm/firmenkunde | crm.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:
| Operation | Quelle 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 /query | je Treffer der gespeicherte Typ, siehe Suche in zwei Phasen |
Alle Operationen im Ablauf
Wann: POST /crm/kunde/create
-
1Client→CDMSschickt
{ "data": { "@type": "crm.privatkunde", "name": "Anna Muster", "geburtsdatum": "1990-04-01" }, "response": ["+"] } -
2CDMSliest
@typeund damit die Payload des Privatkunden, inklusive seiner eigenen FelderOhne@typeweiß CDMS nicht, welcher Untertyp gemeint ist: 400missing-type-for-abstract-field|data. -
3CDMSgibt die Anfrage an die API
/crm/privatkundeweiter -
4CDMS→Databaselegt 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}
-
1Client→CDMSschickt nur die
id, kein@typenötig -
2CDMS→Databaseliest den gespeicherten Typ zur
idnach -
3CDMSgibt 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
-
1CDMSPhase 1 filtert, sortiert und blättert auf dem Obermodell. Sie legt fest, welche Objekte in welcher Reihenfolge kommen, und liefert
totalCount -
2CDMSPhase 2 liest jede Zeile über die API ihres Untertyps, mit dessen Rechten und FilternDie 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. -
3CDMSJede 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“ -
4CDMS→Clientsetzt die Liste in der Reihenfolge aus Phase 1 zusammenErgebnis: Eine gemischte Liste, jedes Objekt mit seinem
@typeund 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 kunde | Tabelle privatkunde | |||
|---|---|---|---|---|
id | _MODELTYPE | name | id | geburtsdatum |
| 5a2b… | crm.privatkunde | Anna Muster | 5a2b… | 1990-04-01 |
| 7c1d… | crm.firmenkunde | Muster GmbH | – | – |
Die Firma steht mit ihren eigenen Feldern in der Tabelle firmenkunde.