CodamAIDocs
Topicdone

Hooks: types and timing

Which hook points exist (before and after the database change, per operation) and how a hook is registered.

Variants
CREATEUPDATEPATCHDELETEROLLBACKREAD (after loading, before the response)beforeafterorder via @Ordermodel hook and field hook

What this is about

A hook is a class in your project that CDMS calls at fixed points of a standard flow. This is how you plug in your own subject-area logic without touching generated code: assign a customer number, check a business rule, decrypt a field for the response.

This page describes the model hook. It always belongs to one model. CDMS calls it for every object of this model that a request creates, changes, deletes, rolls back or reads. It also tells the hook which operation is running. Logic for a single value that you need on many fields, such as encrypting, is a field hook.

Writing and registering a hook

A hook is a Spring bean that implements HookServiceInterface<…Entity> for the entity of the model:

@Service
@Order(100)
public class OrderHook implements HookServiceInterface<OrderEntity> {

  @Override
  public void beforeDatabaseChange(OrderEntity item, CmsMethods method)
      throws AbstractCodamaiException {
    if (method == CmsMethods.CREATE && item.getOrderNr() == null) {
      item.setOrderNr(nextNumber());
    }
  }

  @Override
  public void afterDatabaseChange(OrderEntity item, CmsMethods method)
      throws AbstractCodamaiException {
    if (method == CmsMethods.READ) {
      // e.g. convert a value for the response
    }
  }
}
  1. 1
    Developer
    writes the class under the base package of the project and makes it a bean with @Service
    Spring only finds beans under com.codamai. The base package from the project scaffold lies there.
  2. 2
    Developer
    puts @Order directly on the class when the order of several hooks matters
  3. 3
    CDMS
    on the first object of a model, looks up all beans that implement HookServiceInterface for exactly this entity
  4. 4
    CDMS
    sorts them by @Order and remembers the list
  5. 5
    Hook
    is called from now on for every object of this model

The interface has exactly two methods: beforeDatabaseChange and afterDatabaseChange.

In the hub you can add a hook to a model. This entry only announces the logic. What actually runs is decided by the class in your project alone. Where else custom code belongs is described in Generated code and custom code.

A hook applies to the entity in its type parameter. With abstract models, CDMS always works with the concrete subtype, so the hook of the subtype runs, for example PrivatkundeEntity.

The operations

The second parameter method tells you what is happening. A hook receives all operations and decides by itself which ones it reacts to.

CmsMethodstriggered bybeforeafter
CREATEcreate, also every new child in a nested writeyesyes
UPDATEPUT, also every child that is changed along through a relation with UPDATEyesyes
PATCHPATCH, also every child that is changed alongyesyes
DELETEdelete, also every dependent child that is deleted alongyesyes
ROLLBACKrolling back to an earlier revisionyesyes
READread by id, search, expanded references, read-back after a write–yes

A search also reports itself as READ, just like reading a single object.

The timeline of a write operation

sequenceDiagram
    participant C as Client
    participant D as CDMS
    participant H as Hook
    participant DB as Database
    C->>D: POST /order/create
    D->>D: check roles, transfer fields, collect violations
    D->>H: before (CREATE)
    Note over H: may still change the object
    D->>D: check violations again → otherwise 422
    D->>DB: hand over the object, the id is assigned
    D->>H: after (CREATE)
    D->>DB: flush: execute SQL
    D->>DB: read back
    D->>H: after (READ) for every object read
    D->>DB: commit
    D-->>C: 200 with the object

So a write operation usually triggers two kinds of hooks: those of the write operation and then READ when reading back for the response. Only DELETE reads nothing back. The steps in detail are in The order within a write operation.

before and after

The two hook points
before
beforeDatabaseChange
  • runs after CDMS has transferred all fields, before validation
  • new objects have no id yet
  • with PUT and PATCH the new values are already on the object
  • changes to the object are saved, and still checked if the field had a violation before
  • typical: derive values, check business rules, reject
after
afterDatabaseChange
  • runs after CDMS has handed the object to the database, before the flush and before the commit
  • new objects have their id
  • changes to the object are still saved, but no longer validated
  • typical: prepare follow-up actions that need the id
  • with READ: adjust values for the response only

Both points lie inside the transaction. If a hook throws an error, the whole request is rolled back. What reaches the client then is described in When a hook fails.

All operations in detail

What the hook sees per operation

When: POST /create, or a new child without id in a nested write

  1. 1
    Hook
    before: the new object with the sent values and the default values, without id
  2. 2
    Hook
    after: the same object, now with id

Result: A before hook can fill a required field that the client does not even know, for example an order number. See Creating an object.

When: PUT /update/{id}, or a child with id that is changed along through a relation with UPDATE

  1. 1
    Hook
    before: the stored object, into which CDMS has already transferred the sent values
  2. 2
    Hook
    after: the same object after it was handed to the database

Result: You no longer see the old value of a field on the object. See Replacing with PUT.

When: PATCH /update/{id}, or a child that is changed along

  1. 1
    Hook
    before: the stored object with the changed fields
  2. 2
    Hook
    after: the same object after it was handed to the database

Result: The operation is called PATCH, not UPDATE. If your hook should run on every change, check for both. See Changing with PATCH.

When: DELETE /delete/{id}, every dependent child deleted along, and every dependent child that drops out of its parent through PUT or PATCH

  1. 1
    Hook
    before: the object with its fields, before it disappears from the database
    If the hook throws an error, everything stays as it was.
  2. 2
    Hook
    after: the same object, handed over for removal

Result: See How a DELETE runs.

When: POST {basis}/{id}/rollback/{revision}, only for the addressed object

  1. 1
    Hook
    before: the object in its current state
  2. 2
    CDMS→Database
    restores the revision
  3. 3
    Hook
    after: the object in its restored state

Result: An operation of its own, so that your hook can tell a rollback from an ordinary change. See Rolling back to an old state.

When: every object CDMS loads from the database: read by id, every row of a search, every expanded reference, the read-back after a write

  1. 1
    CDMS→Database
    loads the requested fields
  2. 2
    Hook
    after: the loaded object, before it becomes the response
    The object only contains the fields the request asked for. All others are empty.
  3. 3
    CDMS→Client
    response with the values the hook left

Result: What the hook changes here only ends up in the response, not in the database. The history of an object does not pass through the READ hook. See Reading an object.

Several hooks on one model: @Order

You may write several hooks for one model, for example one for assigning numbers and one for a business check. @Order sets their order: smaller number first.

Two hooks on OrderEntity
  1. 1
    Hook
    @Order(10) NumberHook: assigns the order number
  2. 2
    Hook
    @Order(20) CheckHook: checks whether the order fits the customer, and already sees the number

The order applies per object: for each object, all hooks of its model run one after the other, then the next object follows. The order in which the objects of a nested write come up is described in Hooks for nested objects and cascades.

Decision table

Which hook point fits?
What you want to doHook point
derive a field or fill a required fieldbefore, CREATE (and UPDATE/PATCH if it can change)
check a business rule and rejectbefore, with an exception
reuse the new idafter, CREATE
prevent something from being deletedbefore, DELETE, with an exception
convert a value for the response onlyafter, READ
store a value encrypted, or treat it the same way on many fieldsfield hook
send an email as soon as the change is safely savedno hook point lies after the commit, see pitfalls

Pitfalls

Where to go next

Sources in the code and the knowledge base
  • commons – global/commons/interfaces/HookServiceInterface, HookFieldInterface, enumerations/CmsMethods
  • CDMS/cdms-system-layer – HookManagementSystem (callHooksBeforeDatabase, callHooksAfterDatabase, modelHooksOf: getBeanNamesForType, sorting by @Order, no Spring profile); session/HookRequestContext (addHookBefore, addHookAfter, runAllBefore, runAllAfter)
  • CDMS/cdms-system-layer – AbstractSystemLayer and AbstractSystemSingletonLayer (createObject, updateObject, patchObject, deleteObject, historyRollback); AbstractLayer (recursiveCreate, recursiveUpdate, recursivePatch, recursiveDelete, recursiveRead, recursiveQuery)
  • CDMS/cdms-persistence-database – AbstractDatabasePersistence.queryObjects (objects built from the columns read)
  • CDMS/cdms-integrationtest – TestHookManagement, AbstractHookValidationTest, AbstractReadHookTest, AbstractSingletonHookValidationTest, AbstractRecursiveDelete
  • documentation/60-erweiterung/01-hooks.md
Search