What this is about
Every time CDMS reads or writes an object, it has to know which database the access goes to. That database is called the persistence target. There are only two kinds of targets:
- the system database with the key
system, - the database of a tenant with the tenant key, for example
acme.
The decision is made for each model separately, not once per request. Two things determine it: the level of the model and the tenant in the RequestContext. A header, a field in the data or a URL parameter play no part.
The flow
flowchart TD
A(["Access to a model"]) --> S{"System model?"}
S -- yes --> SYS[("System database")]
S -- no --> K{"RequestContext present?"}
K -- no --> E500["500 CDMS_PERSISTENCE_CONTEXT_MISSING"]
K -- yes --> T{"Tenant set?"}
T -- no --> M1{"Operating mode?"}
M1 -- SINGLE --> ONE[("the one database")]
M1 -- MULTI --> E400["400 CDMS_TENANT_REQUIRED"]
T -- yes --> M2{"Operating mode?"}
M2 -- SINGLE --> ONE
M2 -- MULTI --> R{"reserved key?<br/>system, single"}
R -- yes --> E403R["403 CDMS_TENANT_KEY_RESERVED"]
R -- no --> L{"Tenant and switch target<br/>in the list of allowed tenants?"}
L -- no --> E403["403 CDMS_TENANT_SWITCH_NOT_AUTHORIZED"]
L -- yes --> TDB[("Database of the tenant")]
The order is deliberate: the question “system model?” comes before any check of the context. A missing or wrong RequestContext can therefore neither redirect a system model to another database nor block it.
The decision table
| Level of the model | RequestContext | Tenant | Operating mode | in the list of allowed tenants | Persistence target |
|---|---|---|---|---|---|
| System | – | – | – | – | system database |
| Tenant / user | missing | – | – | – | 500 CDMS_PERSISTENCE_CONTEXT_MISSING |
| Tenant / user | present | missing | SINGLE | – | the one database |
| Tenant / user | present | missing | MULTI | – | 400 CDMS_TENANT_REQUIRED |
| Tenant / user | present | acme | SINGLE | – | the one database; the tenant does not count |
| Tenant / user | present | acme | MULTI | yes | database acme |
| Tenant / user | present | acme | MULTI | no | 403 CDMS_TENANT_SWITCH_NOT_AUTHORIZED |
| Tenant / user | present | system or single | MULTI | – | 403 CDMS_TENANT_KEY_RESERVED, even if the key is in the list |
You set the level of a model in the hub, see Model levels. The generator turns it into the base class of the entity: AbstractSystemModel, AbstractTenantModel or AbstractUserModel. At runtime CDMS reads from it whether a model is a system model.
The error cases
When: Code accesses a tenant model while no RequestContext exists.
-
1CDMSlooks for the RequestContext of the current thread and finds none
-
2CDMS500
CDMS_PERSISTENCE_CONTEXT_MISSING
Result: This does not happen with normal requests but in custom code: an own thread, a scheduled job, a listener. Such code has to obtain the context itself, see Working for a tenant without a request.
When: MULTI, the RequestContext contains no tenant.
-
1CDMSwants to read or write a tenant model
-
2CDMS400
CDMS_TENANT_REQUIRED
Result: Normal requests without a tenant are already refused by the filter chain with 403, see Where the tenant of a request comes from. This check is the second safeguard behind it.
When: MULTI, the tenant or the target of a switch wish is not in the list of allowed tenants.
-
1CDMScompares tenant and switch target with the list from the token
-
2CDMS403
CDMS_TENANT_SWITCH_NOT_AUTHORIZED
Result: A refused switch thus becomes visible instead of silently continuing in the own tenant. See Tenant switch by header.
From tenant key to database
Once the target is fixed, CDMS builds the connection from it. The database URL of the installation contains the placeholder {tenant}:
CODAMAI_PERSISTENCE_DATABASE_URL=jdbc:mysql://db:3306/{tenant}
| Target | Database |
|---|---|
system database in MULTI | jdbc:mysql://db:3306/system |
tenant acme in MULTI | jdbc:mysql://db:3306/acme |
everything in SINGLE | jdbc:mysql://db:3306/single |
So all databases live on the same database server and use the same credentials. They are separated by the database name.
If the database does not exist yet or the server cannot be reached, the access ends with its own error, see Databases, pools, migration:
| Error | Status | Meaning |
|---|---|---|
CDMS_TENANT_DATASOURCE_NOT_FOUND | 500 | The tenant’s database is missing and automatic creation is not approved. |
CDMS_TENANT_DATASOURCE_UNAVAILABLE | 503 | The database server does not answer. CDMS then does not try to create anything. |
CDMS_ENTITY_MANAGER_CREATION_FAILED | 500 | The database exists, but access to it could not be set up. |
One request, two targets
A request can touch both kinds of targets, for example when a hook on a tenant model also writes a system model. CDMS then opens a separate connection and a separate transaction for each target. What that means for errors is explained in No atomicity across two databases.
Pitfalls
Where to go next
- Where the tenant in the RequestContext comes from: Where the tenant of a request comes from
- Which models belong on which level: Model levels
- The rules behind this table: Rules that are never broken