Skip to main content
Version: Current

Event Handler - basics

Let's make things really simple.

  • The Event Handler is the component that enables the application to write to the database.
  • You define your application's Event Handler in a Kotlin script file:
    server/appname/src/main/genesis/scripts/appname-eventhandler.kts
  • In this file, you define specific eventHandler codeblocks, each of which has full access to the database.
  • Each eventHandler can be invoked from the front end, from other eventHandler codeblocks, or from custom components in the application.
  • If you use AppGen to build from your dictionary, then a basic .kts file will be built automatically for you, creating basic insert, modify and delete eventHandler code blocks for all the tables and views in your data model. You can edit this file to customise the component.
  • Otherwise, you can build your .kts by defining each eventHandler codeblock from scratch.
  • For complex Event Handlers, you might prefer to use the Event Handler API to implement a set of classes.

On this page, we'll take you through the basics of creating an Event Handler in simple stages:

By the end of that, you'll have some very useful knowledge, and you'll be ready to move on to the advanced page.

It is possible to define an Event Handler in Java, and we have included Java examples. However, we strongly advise you to create your Event Handler using Kotlin; it is a considerably more efficient method.

A simple example of an Event Handler

Here is a simple example of an Event Handler file. It defines a single eventHandler, which inserts a counterparty into the database, using the entityDb.

  • First, note that there is an eventHandler statement for containing all your eventHandlercodeblocks. This is necessary whether the file contains one eventHandler or multiple ones.
  • For this example, the file defines a single eventHandler of type <Counterparty>. ( <Counterparty> is an object that was created from the COUNTERPARTY table in the schema; this object defines the input that should be expected, such as Counterparty name and Counterparty ID.)
  • The eventHandler performs a single action - it inserts a counterparty as a new row into the database, using the entityDb.
    eventHandler {
eventHandler<Counterparty> {
onCommit { event ->
val counterparty = event.details
entityDb.insert(counterparty)
ack()
}
}
}

Adding a name

Every eventHandler in your .kts must have a unique name. If you do not provide one, it will be allocated automatically. In the previous example, the eventHandler will automatically be named EVENT_COUNTERPARTY.

It is good practice to provide your own name for each eventHandler. For example, if you have insert and modify codeblocks for the same table and you don't name them, then the platform will probably generate identical names for both - which will give you a problem. Note that the prefix EVENT_ is automatically added to the name that you specify.

If you do not define a name, a default name will be allocated automatically. The default name will be EVENT_<message type name>. So, for a message type declared as OrderInsert, declaring an Event Handler block as eventHandler<OrderInsert>{} automatically registers the event with the name EVENT_ORDER_INSERT.

So, below, we modify our previous example by defining the name of the eventHandler: COUNTERPARTY_INSERT. This will automatically become EVENT_COUNTERPARTY_INSERT.

eventHandler<Counterparty>(name = "COUNTERPARTY_INSERT") {
onCommit { event ->
val counterparty = event.details
entityDb.insert(counterparty)
ack()
}
}

Adding validation

So far, we have provided an onCommitblock in our eventHandler. This is where the active instructions are; these are usually database changes.

If you want to provide some validation before the action, you need to have an onValidate block before the onCommit. The last value of the code block must always be the return message type.

In this case, we have used the kotlin require function to check that the counterparty field is not empty.

The onCommit block will only be executed if the counterparty field is not null.

    eventHandler<Counterparty>(name = "COUNTERPARTY_INSERT") {
onValidate { event ->
val counterparty = event.details
require(counterparty.name != null) { "Counterparty should have a name" }
ack()
}
onCommit { event ->
val counterparty = event.details
entityDb.insert(counterparty)
ack()
}
}

Returning a value

The onCommit block must always return either an ack() or nack(...). In the previous examples, it has always been an ack().

Now consider a scenario where you might want to return a nack(...). In this case, there is no onValidate block.

In the onCommit block:

  • if the counterparty field is empty, the eventHandler returns a nack, along with a suitable message.
  • if the counterparty field has content, then the eventHandler returns an ack
eventHandler<Counterparty>(name = "COUNTERPARTY_INSERT") {
onCommit { event ->
val counterparty = event.details
if (counterparty.name == null) {
nack("Counterparty should have a name")
} else {
entityDb.insert(counterparty)
ack()
}
}
}

Default reply types

So far, we have seen ack and nack. There is a third type: warningNack. Let's stop and look at the specifications for all three default reply types:

  • ack: used to signify a successful result. ack takes an optional parameter of List<Map<String, Any>>. For example, ack(listOf(mapOf("TRADE_ID", "1"))).
  • nack: used to signify an unsuccessful result. nack accepts either a String parameter or a Throwable. For example, nack("Error!") or nack(myThrowable).
  • warningNack: used to warn the client. warningNack, like nack, accepts either a String parameter or a Throwable. For example, warningNack("Provided User alias $userAlias will override Username $username.") or warningNack(myThrowable).

Transactional Event Handlers (ACID)

If you want your eventHandler to comply with ACID, you can declare it to be transactional = true. Any exception returned will result in a complete rollback of all parts of the onCommit and onValidate (the transaction also covers read commands) blocks. While an exception will trigger a rollback, the transaction will commit if a nack or ack is returned.

    eventHandler<Counterparty>(transactional = true) {
onCommit { event ->
val counterparty = event.details
entityDb.insert(counterparty)
ack()
}
}

Whether it is a database update or uploading a report to a third party. It will be called when an event message is received with validate = false and has successfully passed the onValidate block. The last value of the code block must always be the return message type.

Processing onValidate and onCommit

The incoming message that triggers an Event Handler can have validate set to true or false. This controls whether the Event Handler simply performs some validation or it executes its complete set of processing.

validate = true

The key thing about this setting is that it means that only the onValidate block is executed, not the onCommit block. Here is the precise process flow:

validate = false

With this setting, both the onValidate codeblock and the onCommit codeblock will be executed. Here is the precise process flow:

More information about onValidate

As you will have seen above, an onValidate codeblock will be executed whether the incoming message has validate=true or validate=false.The onValidate block is optional if you are using the default reply message type (EventReply) and will automatically be successful if not defined.

However, note that an onValidate codeblock is mandatory when using custom reply message types; the script will not compile if there is no onValidate codeblock. See the simple example below:

    eventHandler<Company>(name = "COMPANY_INSERT") {
onValidate { event ->
val company = event.details
require(company.companyName != "MY_COMPANY") {
"We don't accept your company"
}
ack()
}
onCommit { event ->
val company = event.details
val result = entityDb.insert(company)
ack(listOf(mapOf("COMPANY_ID" to result.record.companyId)))
}
}

Kotlin’s require method throws an exception with a message if the boolean expression is not what is expected, and the Event Handler automatically converts that exception into a corresponding EventNack.

Context Event Handlers

In order to optimise database look-up operations, you might want to use data obtained by the onValidate block inside your onCommit block. To do this, use context Event Handlers, as shown below:

    contextEventHandler<Company, String>(name = "CONTEXT_COMPANY_INSERT") {
onValidate {
val company = it.details
if(company.companyName == "MY_COMPANY") {
validationAck(validationContext = "Best company in the world")
} else {
validationAck()
}
}
onCommit { event, context ->
val parsedContext = context ?: "Missing context"
val company = event.details
val result = entityDb.insert(company)
ack(listOf(mapOf("VALUE" to parsedContext)))
}
}

As the example shows, there is an additional type defined for the context Event Handler. This is a String. It gives you the option of returning a String value from the onValidate block (see validationAck logic), which can then be captured it in the onCommit block (see context lambda parameter).

Because the example creates a validation context, the function validationAck() is used at the end of the onValidate block, and not just ack().

Handling delete events

There are two ways of handling a delete event:

  • you can use an index entity as metadata
  • you can create a separate class

Using the index entity reduces code and is also more robust in case of changes to the fields in the key.

Here is an example of using an index entity to handle a delete event:

eventHandler<Trade.ById>(name = "TRADE_DELETE") {
permissioning {
permissionCodes = listOf("TRADER")
}
onValidate { event ->
ack()
}
onCommit { event ->
val trade = event.details
entityDb.delete(trade)
ack()
}
}

Auto-generated REST endpoints

As we have mentioned before, all events created in the eventhandler.kts file are automatically exposed as a REST endpoints.

Below you see an example of accessing a custom end-point called EVENT_TRADE_INSERT running locally. It was automatically generated by the Genesis platform when you defined the event TRADE_INSERT in the app_name-eventhandler.kts.

[POST] http://localhost:9064/EVENT_TRADE_INSERT/

You can see how to manipulate and work with auto-generated endpoints in our pages on REST endpoints.

Custom Log messages

Genesis provides a class called LOG that can be used to insert custom log messages. This class provides you with 5 methods to be used accordingly: info, warn, error,trade,debug. To use these methods, you need to provide, as an input, a string that will be inserted into the Logs.

Here is an example where you can insert an info message into the log file:

LOG.info("This is an info message")

The LOG instance is a Logger from SLF4J.

note

In order to see these messages, you must set the logging level accordingly. You can do this using the logLevel command.