CodamAIDocs
Topicdone

A hook writes itself

When a hook creates something through a system layer itself, that is a write inside a write. Each gets its own level: its own checks, its own hooks, one shared commit. The depth is bounded and measurable.

Variants
hook creates a valid recordouter record is invalidinner record is invalidafter hooks of both levelsinner create with LENIENThook writes on every write (too deep)setting the maximum depthdepth in monitoring

What this is about

A hook can do more than set fields. Through the system layer of a model it can write itself: create a log entry, produce a derived record, change another object. That is a second write running in the middle of the first one.

CDMS treats every write as a level of its own. The request’s write is level 1, a write from one of its hooks is level 2, and so on.

Two levels in the flow

Example: a before hook on Order creates a log entry for every new order.

sequenceDiagram
    participant C as Client
    participant S1 as System layer Order (level 1)
    participant H as Before hook
    participant S2 as System layer Log (level 2)
    participant DB as Database
    C->>S1: POST /order/create
    S1->>S1: build the graph, collect rule violations
    S1->>H: before hooks
    H->>S2: createObject(Log)
    S2->>S2: own rules, own before hooks, own report
    S2->>DB: write Log (no commit)
    S2->>S2: own after hooks
    S2-->>H: Log
    H-->>S1: done
    S1->>S1: report own rule violations (422 if any)
    S1->>DB: write Order
    S1->>S1: own after hooks
    S1-->>C: 200
    Note over S1,DB: commit at the end of the request, for both

What holds for each level:

Level 1 (Order)Level 2 (Log)
Rule violationsonly its own, reported before Order is writtenonly its own, reported before Log is written
Before hooksthose of Order and its childrenthose of Log and its children
After hooksrun after Order is writtenrun after Log is written
Permissionsclass role of Order, field grants of the descentclass role of Log; a field grant from level 1 does not apply here
Commitat the end of the requestnone, it belongs to level 1

What is stored in the end

A hook creates a log entry: what is in the database afterwards?
OrderLog entryResult
validvalid200, both stored
breaks a rulevalid422 with the order's violations, nothing stored
validbreaks a rule422 with the log entry's violations, nothing stored
eitherhook throwsresponse according to the exception, see When a hook fails, nothing stored

An inner create that asks for createReadMode: LENIENT does not commit either. LENIENT applies only to the request’s write, see Create and read back: STRICT or LENIENT. Otherwise the log entry would be durable before the order has been checked.

How deep it may go

A hook that writes again on every write of its own model would never stop. CDMS therefore bounds the depth:

A hook on Order creates a new Order on every write
  1. 1
    Client→CDMS
    POST /api/rest/order/create
  2. 2
    CDMS
    level 1: Order, the before hook creates an Order
  3. 3
    CDMS
    level 2, 3, … up to the maximum depth: each Order creates the next one
  4. 4
    CDMS
    The next level would be beyond the maximum depth
  5. 5
    CDMS
    HookExecutionException write-nesting-too-deep|10 → 500
  6. 6
    CDMS→Database
    the whole request is rolled back, not a single Order remains

You set the maximum depth in application.yaml:

codamai:
  cdms:
    api:
      max-write-nesting-depth: 10   # level 1 is the request's write

The response is 500, not 4xx: the client did nothing wrong, the cause is a hook.

In monitoring

If the application has Micrometer and a registry, CDMS measures every request-level write:

MetricKindMeaning
cdms.write.nesting.depthdistributionthe deepest level a write reached; 1 means no hook wrote
cdms.write.nesting.refusedcounterwrites refused for too much depth

Both carry the model of the level-1 write as the tag model. A maximum that slowly rises means hooks are calling hooks. Every refusal is also logged as a warning.

Pitfalls

What comes next

Sources in the code and the knowledge base
  • CDMS/cdms-system-layer – session/WriteNestingContext (enter, leave, isNested), ValidationRequestContext, FieldAccessContext, HookRequestContext (suspend, resume)
  • CDMS/cdms-system-layer – AbstractLayer (enterWrite, leaveWrite), AbstractSystemLayer, AbstractSystemSingletonLayer (createObject, updateObject, patchObject, deleteObject, historyRollback)
  • CDMS/cdms-system-layer – configurations/SystemSettings (maxWriteNestingDepth), observability/MicrometerWriteNestingObservability
  • CDMS/cdms-system-layer – docs/adr/adr-016-verschachtelte-schreibvorgaenge-haben-eigene-ebenen.md
  • CDMS/cdms-integrationtest – AbstractNestedWriteTest, NestedWriteSingleDbTest, NestedWriteMultiDbTest
  • documentation/60-erweiterung/01-hooks.md
Search