CodamAIDocs
Topicdone

Abstract models and @type

A general concept with several concrete types, such as “customer” with private and business customers. This page explains how the hub API forwards requests to the right subtype and when the client has to send @type.

Variants
create with @typereplace and update with @typeread and delete: type from the idsearch in two phasesparallel read stepssubtype endpoints directlypermissions and filters of the subtypesunknown type

What this is about

Sometimes a thing comes in several kinds that have a lot in common, but not everything. A customer can be a private customer (with a date of birth) or a business customer (with a commercial register number). Both have a name, an address and orders.

That is what abstract models are for: a parent model with the shared fields and several concrete child models that inherit from it.

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

The type: @type

Every object carries its concrete type. In the database it is stored in the _MODELTYPE column; in JSON it is called @type. The value is the API path of the child model with dots instead of slashes:

Child modelAPI path@type
private customer/crm/privatkundecrm.privatkunde
business customer/crm/firmenkundecrm.firmenkunde

The hub API: one entrance, several targets

For the abstract model, the generator creates a hub API with the usual paths, here /api/rest/crm/kunde/…. It does nothing itself; it forwards every request to the API of the right subtype. Each subtype also has its own normal 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 -.->|directly| P
    C -.->|directly| F

Where the hub API gets the type from depends on the operation:

How does the hub API find the subtype?
OperationSource of the type
POST /create@type in the client's data
PUT /update/{id}@type in the client's data
PATCH /update/{id}@type in the client's data
POST /read/{id}, GET /read/{id}the server looks up the stored type for the id
DELETE /delete/{id}the server looks up the stored type for the id
POST /querythe stored type of each match, see search in two phases

Every operation step by step

Requests to the hub API

When: POST /crm/kunde/create

  1. 1
    Client→CDMS
    sends { "data": { "@type": "crm.privatkunde", "name": "Anna Muster", "geburtsdatum": "1990-04-01" }, "response": ["+"] }
  2. 2
    CDMS
    reads @type and with it the private customer's payload, including its own fields
    Without @type, CDMS does not know which subtype is meant: 400 missing-type-for-abstract-field|data.
  3. 3
    CDMS
    forwards the request to the API /crm/privatkunde
  4. 4
    CDMS→Database
    creates the object, with the permissions, rules and hooks of the private customer

Result: Response as for the subtype, with "@type": "crm.privatkunde".

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

  1. 1
    Client→CDMS
    sends only the id, no @type needed
  2. 2
    CDMS→Database
    looks up the stored type for the id
  3. 3
    CDMS
    forwards the request to the API of that subtype

Result: When reading, the response contains @type so the client can tell the kinds apart.

When: PUT and PATCH /update/{id}

As for create, @type must be in data, for PATCH too. It must be the type the object already has: an object never changes its type.

When: /crm/privatkunde/…

You can also address every subtype directly through its own API. Then you need no @type, and the search returns only objects of that subtype. You need the hub API when you want to read or search all kinds together.

Searching across all subtypes: two phases

A search on the hub API has to return objects of different types in one list, correctly sorted and paged. That happens in two phases:

sequenceDiagram
    participant C as Client
    participant H as Hub API /crm/kunde
    participant DB as Database
    participant P as API privatkunde
    participant F as API firmenkunde
    C->>H: POST /query (filter, sorting, page)
    Note over H,DB: Phase 1: only IDs and types
    H->>DB: SELECT id, _MODELTYPE ... WHERE ... ORDER BY ... LIMIT
    DB-->>H: [ (id1, private), (id2, business) ] + totalCount
    Note over H,F: Phase 2: read each row through its subtype
    par in parallel
        H->>P: read id1
    and
        H->>F: read id2
    end
    P-->>H: object 1
    F-->>H: object 2
    H-->>C: data in the order of phase 1 + meta
What matters here
  1. 1
    CDMS
    Phase 1 filters, sorts and pages on the parent model. It decides which objects come in which order, and returns totalCount
  2. 2
    CDMS
    Phase 2 reads each row through the API of its subtype, with that subtype's permissions and filters
    The read steps run in parallel, by default up to 8 at a time (setting cdms_hub_query_parallelism). If a write transaction is already open in the same request, CDMS reads them one after another.
  3. 3
    CDMS
    Each row gets its own copy of the field selection. So a + means “all fields of the private customer” for a private customer and “all fields of the business customer” for a business customer
  4. 4
    CDMS→Client
    assembles the list in the order from phase 1
    Result: A mixed list, each object with its @type and its own fields.

Permissions and filters

Each subtype is a complete model with roles of its own. Whoever reads through the hub API therefore needs the read permissions of the subtype the object belongs to. Attribute filters of the parent model automatically apply to all subtypes as well: a subtype is never a way around the rules of the parent model.

In the database

The parent model has a table of its own with the shared fields and the _MODELTYPE column. Each subtype has its own table with its additional fields, linked through the same id.

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

The company is stored with its own fields in the firmenkunde table.

Pitfalls

Sources in the code and the knowledge base
  • CDMS/cdms-rest-api – AbstractHubApi (getApiLayer, queryData, readRow, getModelType)
  • CDMS/cdms-generator – ApiHubProcessor, EntityProcessor (JOINED, _MODELTYPE, DiscriminatorValue), RestPayloadProcessor (JsonTypeInfo @type), CdmsYamlLoader (attribute filters of parent models)
  • documentation/30-daten-und-persistenz/04-vererbung-und-abstrakte-modelle.md
Search