CodamAIDocs
Topicdone

Model levels: system, tenant, user

Every model belongs to one of three levels. The level decides which database holds the data and whether a person only sees their own rows.

Variants
SYSTEM (always the system DB)TENANT (tenant DB)USER (tenant DB + owner _userId)operating mode MULTIoperating mode SINGLEnot specified → TENANTsubtypes inherit the levelrelations across levelstechnical client and USER models

What this is about

When you create a model in the hub, the first thing you choose is its scope, the level its data lives on. There are exactly three:

The three levels
System
Scope “system-wide”, SYSTEM
  • applies to the whole installation
  • always stored in the system database
  • every tenant sees the same data
  • Example: country list, the platform's product catalog
Tenant
Scope “tenant”, TENANT
  • belongs to one customer
  • stored in that tenant's database
  • every person in the tenant sees the same data (as far as their roles allow)
  • Example: a company's customers, orders, audits
User
Scope “user”, USER
  • belongs to one person
  • also stored in the tenant database
  • each person sees only their own rows
  • Example: personal settings, notes, favorites

Where the data lives

In operating mode MULTI, every tenant has a database of its own. The user level is not a separate database but a refinement inside the tenant database:

flowchart LR
    subgraph SYS["System database"]
        S1["System models<br/>e.g. country list"]
    end
    subgraph TA["Database of tenant A"]
        A1["Tenant models<br/>e.g. customers of A"]
        A2["User models<br/>Anna's rows | Ben's rows"]
    end
    subgraph TB2["Database of tenant B"]
        B1["Tenant models<br/>e.g. customers of B"]
        B2["User models<br/>Clara's rows"]
    end
    SYS ~~~ TA ~~~ TB2

User models have an additional field _userId. On create, CDMS writes the ID of the signed-in person into it, and on read it filters by it.

Which level do I choose?

Two questions are enough:

flowchart LR
    Q1{"Do all customers need<br/>the same data?"}
    Q1 -->|yes| SYS["System"]
    Q1 -->|no| Q2{"Does the data belong to<br/>a single person who is the<br/>only one allowed to see it?"}
    Q2 -->|yes| USR["User"]
    Q2 -->|no| TEN["Tenant"]

What happens on an access

The level takes effect on every read and write, in two places: choosing the database and filtering the rows.

One access on each level

When: The model has the scope “system-wide”.

  1. 1
    Client→CDMS
    reads or writes, with or without a tenant in the token
  2. 2
    CDMS
    sees from the model: system level. The tenant is not even read; a tenant header changes nothing either
  3. 3
    CDMS→System DB
    accesses the system database, without a row filter from the level

Result: Everyone sees the same data, as far as their roles allow.

When: The model has the scope “tenant” (or none).

  1. 1
    Client→CDMS
    reads or writes; the tenant is in the token
  2. 2
    CDMS
    Is there a tenant in the context? May this request access it?
    No tenant in MULTI → 400 CDMS_TENANT_REQUIRED. Tenant not allowed → 403.
  3. 3
    CDMS→Tenant DB
    accesses the database of exactly this tenant

Result: Every person in the tenant sees the same data, as far as their roles allow. Other tenants see none of it.

When: The model has the scope “user”.

  1. 1
    Client→CDMS
    reads or writes; tenant and person are in the token
  2. 2
    CDMS
    chooses the tenant database, exactly as on the tenant level
  3. 3
    CDMS
    on create: sets _userId to the ID of the signed-in person. A value sent by the client is ignored
  4. 4
    CDMS
    on read, search, update, delete: adds the mandatory filter _userId = signed-in person
  5. 5
    CDMS→Tenant DB
    returns or changes only this person's rows

Result: Each person sees only their own rows. From their point of view, other people's rows do not exist: 404, not 403.

Which database? The complete decision

This is how the persistence layer decides on every single access. The order matters: the question “system model?” comes before everything else.

Persistence target per access
System model?Operating modeTenant in the context?Tenant listed in allowedTenants?Target
yes–––System database
noMULTIyesyesDatabase of this tenant
noMULTIyesno403 CDMS_TENANT_SWITCH_NOT_AUTHORIZED
noMULTIno–400 CDMS_TENANT_REQUIRED
noSINGLE––The single shared database

“Allowed” means: the tenant is in the allowedTenants list from the token. If the list is missing, nothing is allowed.

The table shows the view of the persistence layer. In MULTI, a request without a tenant usually never gets that far: the CIAS filter chain already rejects it with 403 tenant-required. The 400 is the second safeguard behind it.

There is no fallback: if the tenant is missing, a tenant object never silently ends up in the system database. Whether a tenant is served at all (active, not suspended) has already been decided before, by the tenant gate in CIAS.

SINGLE and MULTI

The levels exist in both operating modes. Only the way data is stored changes:

MULTISINGLE
Databasesone system DB + one DB per tenantexactly one
System modelssystem DBthe one DB
Tenant modelsthe tenant's DBthe one DB, without separation by customer
User modelsthe tenant's DB, filtered by _userIdthe one DB, filtered by _userId
Tenant required in the token?yes, otherwise 400no

Relations between levels

A relation connects two models, and both must be reachable in the same database. That leads to the rule the hub enforces while you model:

May model A reference model B?
Level of ALevel of BRelation
SystemSystemallowed – both in the system DB
TenantTenantallowed – both in the tenant DB
TenantUserallowed – both in the tenant DB
UserTenantallowed – both in the tenant DB
Tenant or userSystemnot allowed – the target lives in another database
SystemTenant or usernot allowed – the target lives in another database

In the hub, invalid targets are grayed out in the selection (“scope not allowed”) and rejected on save.

How the level is set

From the hub to the class
  1. 1
    Admin
    chooses the Scope field on the model in the hub: “system-wide”, “tenant” or “user”
  2. 2
    CDMS
    stores it as modelType = SYSTEM, TENANT or USER. If the value is missing, TENANT applies
  3. 3
    CDMS
    The generator derives the entity from the matching base class: AbstractSystemModel, AbstractTenantModel or AbstractUserModel
    A subtype of an abstract model inherits the base class of its parent model, and with it the parent's level.
  4. 4
    CDMS
    On startup, CDMS checks that every entity is in the right group. If something does not match, the application does not start

Two safeguards against mix-ups

Two independent mechanisms prevent an object from ending up in the wrong database:

How an object reaches the right database
  1. CDMS
    Routing
    Chooses the database by base class. System models ignore the tenant completely.
    ↳ no 400 / 403, never a fallback to the system DB
  2. CDMS
    Type sets
    Each database connection only knows the models of its level. The system DB does not know tenant models, and vice versa.
    ↳ no a misrouted object fails in Hibernate instead of being stored in the wrong place
  3. CDMS
    Startup check
    Do routing and type sets match?
    ↳ no the application does not start
  4. The object lives in the database of its level

What else depends on the level

TopicSystemTenantUser
Singletonone object for the whole installationone object per tenantone object per person
Filesstorage path without tenantpath with tenantpath with tenant
Historyrevision log of the system DBrevision log of the tenant DBrevision log of the tenant DB
Row filternone from the levelnone from the level; the tenant takes effect through the database_userId = signed-in person

Roles, attribute filters and custom filters apply in addition on every level.

Pitfalls

Sources in the code and the knowledge base
  • CDMS/cdms-persistence-database – AbstractSystemModel, AbstractTenantModel, AbstractUserModel, AbstractEntityModel
  • CDMS/cdms-persistence-database/docs/14-entity-classification-and-managed-types.md
  • commons-persistence – DatabaseRequestContext.resolveTenant
  • CDMS/cdms-system-layer – AbstractLayer (owner filter, _userId on create)
  • CDMS/cdms-generator – EntityProcessor (modelType → base class), CdmsYamlLoader
  • CDMS/frontend – ModelFormFields.vue (scope), itemKinds.ts isRelationScopeCompatible
  • documentation/10-cdms-grundlagen/02-modelle-und-metadaten.md, 30-daten-und-persistenz/01-mandantentrennung.md
Search