CodamAIDocs
Topicdone

Singletons: exactly one object

Some models exist only once per scope, for example the settings of a user. This page explains how the paths without an ID work and what happens on a second create or when the object is missing.

Variants
singleton per systemper tenantper usercreate, read, replace, update, deletesecond create → 400read/update without object → 404delete without object → no effectfile, history and rollback

What this is about

Some data exists only once: a person’s settings, a tenant’s configuration, the basic settings of the whole installation. For these cases you mark a model in the hub as a singleton.

One object per what?

How many objects there are in total is decided by the model level:

The three kinds of singleton
System singleton
Scope “system-wide”
  • one object for the whole installation
  • in the system database
  • Example: basic settings of the platform
Tenant singleton
Scope “tenant”
  • one object per tenant
  • in the tenant database
  • Example: a company's configuration
User singleton
Scope “user”
  • one object per person
  • in the tenant database, filtered by _userId
  • Example: personal settings

The endpoints without an ID

Normal model and singleton side by side
Normal modelSingleton
createPOST /createPOST /create
readPOST /read/{id}POST /read
quick lookGET /read/{id}GET /read
replacePUT /update/{id}PUT /update
updatePATCH /update/{id}PATCH /update
deleteDELETE /delete/{id}DELETE /delete
searchPOST /querydoes not exist
download fileGET /{id}/fileGET /file
historyPOST /{id}/historyPOST /history
rollbackPOST /{id}/rollback/{revision}POST /rollback/{revision}

The object still has an id. It appears in the response, but the client never has to send it.

Only the search is missing, because there is nothing to search for. History and rollback, on the other hand, work exactly as for every other audited model: the history is not a list of objects but the course of one object over time.

How the server finds the one object

Before every operation, the server looks up the existing object, with the same rules as a normal search:

Finding the object
  1. 1
    CDMS
    picks the database by model level (system DB or the one of the tenant from the token)
  2. 2
    CDMS
    adds the filter _userId = signed-in person for user singletons
  3. 3
    CDMS→Database
    looks up the object
  4. 4
    CDMS
    found → its id is used for the operation. Not found → there is none yet

Every operation step by step

What happens on each operation

When: POST /create

  1. 1
    CDMS
    Is there already an object in this scope?
  2. 2
    CDMS→Client
    yes → 400 object-already-exists|use-update
  3. 3
    CDMS→Database
    no → creates the object as for a normal model, with permission check, validation and hooks

Result: The one object now exists.

When: POST /read with response, or GET /read

  1. 1
    CDMS
    looks up the object
  2. 2
    CDMS→Client
    not there → 404 no-object-found
  3. 3
    CDMS→Client
    there → returns it with the requested fields

When: PUT /update

  1. 1
    CDMS
    looks up the object
  2. 2
    CDMS→Client
    not there → 404 no-data-exists|use-create
  3. 3
    CDMS
    there → puts its id into the payload itself and replaces as with a normal PUT

When: PATCH /update

  1. 1
    CDMS
    looks up the object
  2. 2
    CDMS→Client
    not there → 404 no-object-found
  3. 3
    CDMS
    there → puts in the id itself and updates as with a normal PATCH: a missing field stays, null clears

When: DELETE /delete

  1. 1
    CDMS
    looks up the object
  2. 2
    CDMS
    there → deletes it as with a normal DELETE. Not there → nothing to do, no error

Result: Afterwards there is no object; a new create is possible again.

When: GET /file, POST /history

As for a normal model, just without an id in the path. Both answer 404 if there is no object yet. History exists for audited models only.

When: POST /rollback/{revision}, for audited models with a rollback endpoint

  1. 1
    CDMS
    looks up the object
  2. 2
    CDMS→Client
    not there → 404 data-not-found
  3. 3
    CDMS
    there → checks the rollback role, runs the ROLLBACK hooks and restores the named revision, exactly as for a normal model

Result: The old state is back, as a new revision. The revision number comes from the history (revisionMeta.ref).

The lifeline of a singleton

stateDiagram-v2
    direction LR
    [*] --> Empty
    Empty --> Present: POST /create
    Empty --> Empty: read / update / patch → 404
    Present --> Present: read, PUT, PATCH, rollback
    Present --> Present: create → 400 use-update
    Present --> Empty: DELETE

A typical pattern in the client

Because create and update are separate, a settings dialog needs a small switch:

Saving settings
Did GET /read return an object when the dialog opened?Call on save
yesPATCH /update with the changed fields
no (404)POST /create with all fields

Pitfalls

Sources in the code and the knowledge base
  • CDMS/cdms-rest-api – AbstractRestSingletonApi
  • CDMS/cdms-system-layer – AbstractSystemSingletonLayer, AbstractSystemLayer.getSingletonByQuery
  • CDMS/cdms-generator – ApiSingletonProcessor, CdmsYamlLoader (singleton)
  • documentation/10-cdms-grundlagen/02-modelle-und-metadaten.md (Singletons)
Search