Skip to main content
Version: 2.4

Event Bindings

An event binding is a SpEL script that tSM automatically executes when a predefined platform event occurs. Think of it as a server-side trigger written in SpEL.


How it works

  1. Trigger — a user action or API call creates, updates, or deletes an entity.
  2. Matching — the platform finds all binding rows where entityType and eventName match the event. All matching bindings are executed (ordered by script code ascending).
  3. Synchronous (async=false, default) — the script runs inside the same database transaction as the entity change. If the script fails, the entire change is rolled back.
  4. Asynchronous (async=true) — the script is dispatched after the transaction commits. It receives a read-only snapshot; failures are retried with exponential back-off before landing in the dead-letter queue.

Register-based Configuration

Bindings are configured in the register "Scripts / Bindings / Entity Events" (internal code Scripts.Bindings.EntityEvents). Each row corresponds to one binding and is described by the following JSON schema (simplified):

PropertyTypeRequiredDescription
entityTypestringLogical name of the entity that raises the event (e.g. Ticket, Order). Autocomplete comes from the built-in Entity-Type LOV widget.
eventNamestringLifecycle event – one of CREATE, UPDATE, DELETE.
scriptstringCode of the SpEL script to execute, chosen via the Script LOV.
asyncbooleanRun async: dispatch the event after the DB transaction commits (good for heavy work). Default =false (synchronous).
privateEntitybooleanUse internal model: pass the private (managed) entity instance to the script instead of the documented public model. Use only for low-level tweaks; the internal API may change without notice.

Example binding (as stored in the register):

{
"entityType" : "Ticket",
"eventName" : "UPDATE",
"script" : "Ticket.Events.DescriptionToUppercase",
"async" : false
}

Minimal Working Example

  1. Register row – as above.

  2. Script – create a new entry in Scripts with code Ticket.Events.DescriptionToUppercase and source:

    #ticket.description = #ticket.description.uppercase()

Save both. The next time a Ticket UPDATE happens, the description is converted to uppercase.


Authoring the SpEL Script

The binding row only tells which script to run. The script itself lives in the tSM Scripts register (main menu → Scripts). See the Script reference for the full definition and attribute reference, and the SpEL Console guide for developing and debugging scripts interactively.

  • Code – must match the value used in the binding.
  • Source – the actual SpEL text.

Organization – keep event scripts short and focused. If you need more than ~50 lines, delegate into a service class or another script-to-script helper.


Execution Context & Transactionality

  • Scripts run inside the same DB transaction as the change that triggered them – unless async=true, in which case they execute right after commit (read-only snapshot; failures are retried × 3 with exponential back-off before landing in the dead-letter queue).
  • A context variable with the name of the entity type (e.g. #ticket) is available in the script.
  • All standard SpEL helpers work (#now(), #if(), #with(), …).
Transaction deep dive

For a thorough explanation of how transactions affect SpEL scripts — including external calls, the {async: true} flag, and #businessException — see SpEL and Transactions and the Transactions overview.


Context Variables

For every binding tSM injects one primary context variable whose name is the entity type with the first letter lower-cased:

  • Ticket#ticket
  • Order#order
  • ProcessInstance#processInstance

When the binding row has privateEntity=true an additional variable with a Private suffix appears, pointing to the managed Hibernate instance:

  • Ticket#ticketPrivate
  • Order#orderPrivate

The public model (#ticket, #order, …) is still available even in private-entity bindings, so you can mix safe DTO reads with occasional direct manipulations.

You are free to read and write the context object; any changes are flushed to the database unless the binding is asynchronous (async=true) — in that case the object is a detached, read-only snapshot and modifications are not allowed.


Debugging & Testing

  • Kibana – every execution is logged; search by the traceId MDC key to correlate the full flow.

  • Manual log in SpEL:

    // Kibana based logging
    @logger.info("Ticket description changed to " + #ticket.description)

    // tSM Integration Logs
    @log.log.create({
    logTypeCode : "Test.Log",
    request : "Ticket description changed to " + #ticket.description
    })

Performance Tips

PatternAdvice
Heavy IO (HTTP, mail)Mark binding async=true or enqueue work via @taskScheduler.
Long-running loopsBatch queries; aim for < 100 ms per event.
Private vs public entityPrefer public DTO; set privateEntity=true only when you must bypass standard hooks.

Advanced Patterns & Gotchas

Field-specific UPDATE bindings

Short-circuit early when the change is irrelevant:

#if(!#changedFields.containsKey('description')).then('skip')

Multiple bindings on the same event

All bindings matching (entityType,eventName) are executed. Order is deterministic: ascending by the script code.

Async caveat

Async bindings receive a detached, read-only snapshot. Direct modifications to the entity are not allowed; use services instead.

For more details on async behavior and its interaction with the transaction model, see SpEL and Transactions — Async.


See also