What this is about
An abstract model is an umbrella term with several concrete subtypes, for example kunde (customer) with privatkunde (private customer) and firmenkunde (business customer). How this works in general is described under Abstract models and @type.
This page covers the case where a reference points to an abstract model. An order belongs to a customer, and this customer can be a private customer or a business customer. When expanding, CDMS does not know in advance which subtype will come.
The example model
classDiagram
class auftrag {
nummer
kunde
}
class berater {
name
kunden
}
class kunde {
<<abstract>>
name
}
class privatkunde {
geburtsdatum
}
class firmenkunde {
handelsregisternummer
}
auftrag --> kunde : kunde (single)
berater --> kunde : kunden (list)
kunde <|-- privatkunde
kunde <|-- firmenkunde
auftrag is an order, berater an advisor. geburtsdatum is the date of birth, handelsregisternummer the commercial register number.
@type | Fields |
|---|---|
crm.privatkunde | name, geburtsdatum |
crm.firmenkunde | name, handelsregisternummer |
How CDMS resolves the response
While resolving the response, CDMS sees that kunde is abstract. It applies your inner response to each subtype and combines the results:
flowchart TB
R["{ field: kunde, response: ['+'] }"] --> P["for privatkunde:<br/>name, geburtsdatum"]
R --> F["for firmenkunde:<br/>name, handelsregisternummer"]
P --> U["Union:<br/>name, geburtsdatum, handelsregisternummer"]
F --> U
U --> L["Read each object through its subtype:<br/>fields the subtype does not have are dropped"]
So a + in an abstract reference means “all simple fields of all possible subtypes”. When a private customer is read, name and geburtsdatum remain; for a business customer, name and handelsregisternummer.
Single reference and list
When: auftrag.kunde with { "field": "kunde", "response": ["+"] }
-
1CDMS→Databasereads the
auftragand attacheskundewith a LEFT JOIN, taking only theidand the stored typeThe type comes from the_MODELTYPEcolumn of the parent model. -
2CDMSpicks the subtype from the type, e.g.
privatkunde -
3CDMSchecks the read role of this subtype
-
4CDMS→Databasereads the customer through the subtype, with its row filters
-
5CDMSputs it with
@typeinto the fieldkunde
Result: "kunde": { "@type": "crm.privatkunde", "name": …, "geburtsdatum": … }
When: berater.kunden with { "field": "kunden", "response": ["+"], "parameter": { … } }
-
1CDMS→DatabasePhase 1: searches the parent model
kundeforidand type only, with the filter on the advisor, your filter, your sorting and yourlimit -
2CDMSsplits the matches by subtype
-
3CDMS→DatabasePhase 2: reads all matches of each subtype at once, through its permissions and filters
-
4CDMSassembles the list in the order from phase 1, every object with
@type
Result: A mixed list of private and business customers, sorted correctly.
When: { "field": "kunde", "response": ["name", "geburtsdatum"] }, but the customer is a business customer.
A business customer has no geburtsdatum. CDMS silently leaves it out for this object, without an error.
Result: 200 with name, geburtsdatum is missing for this object.
When: The user may read private customers, but not business customers.
Permissions apply per subtype. If the reference points to a business customer, the request fails with 403 and the role of the business customer. If it points to a private customer, it works.
Result: Whether the request works depends on which subtype is in the data.
The result in the example
POST /api/rest/crm/berater/read/b5…
{
"response": [
"name",
{
"field": "kunden",
"response": ["+"],
"parameter": { "order": [{ "field": "name", "order": "ASC" }] }
}
]
}{
"data": {
"id": "b5…",
"name": "Petra Berger",
"kunden": [
{ "@type": "crm.privatkunde", "id": "5a2b…",
"name": "Anna Muster", "geburtsdatum": "1990-04-01" },
{ "@type": "crm.firmenkunde", "id": "7c1d…",
"name": "Muster GmbH", "handelsregisternummer": "HRB 12345" }
]
},
"meta": { "error": false }
}Evaluating @type in the client
@type is the API path of the subtype with dots instead of slashes. Branch on it in the client, not on which fields are filled:
for (const kunde of berater.kunden) {
switch (kunde["@type"]) {
case "crm.privatkunde":
showPrivateCustomer(kunde);
break;
case "crm.firmenkunde":
showBusinessCustomer(kunde);
break;
}
}
Decision table
| stored type | read role of this subtype | Content of kunde |
|---|---|---|
| crm.privatkunde | yes | name, geburtsdatum, @type |
| crm.firmenkunde | yes | name, @type; geburtsdatum is dropped |
| – | no | 403 with the role of the subtype |
| no reference set | – | null |
Pitfalls
Where to go next
- Basics of parent model, subtypes and hub API: Abstract models and
@type - Expanding in general: Expanding references and lists