What this is about
A transaction is a bracket around changes in the database: either all changes inside it become permanent, or none do. Making them permanent is called a commit, discarding them is called a rollback.
In CDMS this bracket is exactly one request. Everything a request writes belongs together: the object itself, all nested children, all objects deleted along, and everything hooks write through CDMS.
The timeline of a request
sequenceDiagram
participant C as Client
participant D as CDMS
participant H as Hook
participant DB as Database
C->>D: PUT /company/update/c4…
D->>DB: first database action opens the transaction
D->>H: before hooks
D->>DB: change, write children
D->>H: after hooks
D->>DB: flush: SQL is executed, not yet committed
D->>DB: read back for the response
D->>DB: COMMIT
D-->>C: 200 with the object
Two points in time need to be kept apart:
- flush: CDMS sends the collected SQL statements to the database. The database checks its rules at this point, for example unique values. Other requests do not see the change yet.
- commit: the database makes the change permanent and visible to everyone. This happens right before CDMS writes the response.
So when a success response reaches the client, the change is already saved. A request right after it sees it.
Success, error, conflict
When: All steps succeed.
-
1CDMSprocesses the request: check, write, hooks, read back
-
2CDMS→Databasecommit, immediately before the response body is written
-
3CDMS→Client2xx
Result: The change is permanent.
When: Any step fails: missing role, validation, hook, database rule.
-
1CDMSa step throws an error
-
2CDMS→Databasemarks the whole request as failed and rolls back all open transactions
-
3CDMS→Clienterror response, e.g. 400, 403, 404, 422
Result: Nothing of this request is saved, not even the parts that were already written before the error.
When: All steps succeed, but another request changed the same file model at the same time. Only file models detect concurrent changes.
-
1CDMS→Databasecommit fails because of the concurrent change
-
2CDMS→Client409, the request is rolled back
Result: Because the commit comes before the response, the client learns about it. See Concurrent changes.
Decision table
| All steps successful? | Commit successful? | Result |
|---|---|---|
| yes | yes | 2xx, everything saved |
| yes | no | 409 for a concurrent change of a file model, otherwise a server error; nothing saved |
| no | – | error response; nothing saved |
Two cases have their own pages:
- Content in the file storage is not part of the transaction, but follows it: it only takes effect after the commit. Files and transaction
- A request that writes system and tenant models has two transactions: No atomicity across two databases
What lies inside the bracket
| What | in the same transaction? |
|---|---|
| the object of the request | yes |
| children created, changed or deleted through nesting | yes |
| unlinked objects (reference cleared) | yes |
| writes of a hook through CDMS to models of the same level | yes |
| the read-back for the response | yes, in STRICT mode |
| file contents in the file storage | no, but they only take effect after the commit and are discarded on an error |
| emails, HTTP calls, messages sent by a hook | no |
Two requests, two transactions
-
1Client→CDMS
POST /customer/create→ 200, the customer is saved -
2Client→CDMS
POST /order/createwith the new customer → 422 -
3CDMSrolls back only the second requestResult: The customer stays, even though their order is missing.
There is no bracket across several requests. If two objects may only come into existence together, write them in one request, nested through the relation. See The four cases in nested writing.
Pitfalls
What comes next
- What happens when reading back after a create fails: Create and read back: STRICT or LENIENT
- Whether the client may retry after an error: May the client retry?
- The path of a request through the layers: The path of a request through the layers