This feature keeps going on with a new paradigm: handle data in an event-driven way. The 4D 21 provides a complete series of events related to database operations (save or drop).
ORDA events can replace triggers and offer many more advantages: more control, allowing you to code your business logic (including time consuming jobs such as printing invoices or storing external data) directly in an ORDA data class function. They respond to events on data level such as new, modify, save, drop (CRUD)
ORDA events offer precise granularity and sophisticated error handling, leading to a strong data integrity and a better code organisation.
Discover how to implement the appropriate business logic in each step of a save or drop action.
ORDA events – A waltz in several times
In this previous blogpost, we unveiled the first event out of a long series: the touched event.
The new database operation events are designed to be used in the ORDA layer through entities handled with the This keyword which is not possible with triggers.
Compared to triggers, those events offer finer granularity for detecting specific database operations steps and sophisticated error handling that can stop the action if needed.
Unlike triggers, events not only allow you to implement actions before the save/drop actions. They can also run actions during and after the save/drop operations.
The error handling is powerful, you can return customized errors with different severities and detailed extra information for the end user.
Previously, you could implement some functions for this, but you had to call them manually everywhere it was necessary.
Now, this feature offers a complete event-driven logic, automatically triggered at the appropriate time. Implement it once and let the 4D engine handle it at run time:
- Inconsistent data can be rejected beforehand without involving the persistence layer
- Interactions with external systems can happen precisely when the 4D data is persisted
- Actions can be triggered when the save/drop fails
And good news, in opposite to triggers, ORDA does not lock the entire underlying table of a dataclass while saving / dropping a entities. Several events can run in parallel as long as they involve distinct entities (i.e. records).
If you rely on a full lock of the entire table to handle unique sequence numbers or similar, you have to handle it on your own, for example with singletons – and this allows you to lock only when needed – and have full control about this process.
The distinct phases of a database operation
When a save or drop action runs, these events allow you to implement business logic in three distinct phases of the action:
- Validating the save or drop action: Useful for checking data consistency regarding this action, e.g.:
- Is the status of the entity about to be dropped set to “TO DELETE” ?
- Is the departure date < arrival date in a booking entity ?
Thanks to this event, you can stop the save/drop action and return an error to the end user. This ensures the data will be consistent and ready before entering the save/drop action itself.
- During saving / dropping: Use this if you need to synchronize your database operations with an external system, e.g.:
- When saving an entity, write data on a disk, create a document on a Google Drive, etc.
In this event, you can return errors in case of failure to stop the action. These errors can then be handled in the after event described below.
- After the save or drop action: Here you can take measures regarding potential errors raised during the save/drop actions, e.g.:
- Mark a product as “To be checked” because an error occurred during the drop action
Thanks to these events, each step of the save or drop action can have its own dedicated business logic.
These events are triggered as soon as the database operation is triggered using ORDA or the REST server.
Watch the video below. It goes through the provided HDI with more details.
let’s see this in action with examples
where to implement events
ORDA events must be implemented in the Entity class using the event keyword.
the validate events
These events are triggered before the save or drop action and can stop the action by returning an error. In this case, the other events described below (except the after event) are not triggered.
They can be implemented:
- At the attribute level: to check a specific attribute’s consistency
- At the entity level: to check the entity content globally or compare attributes
Example:
In this example, in the ProductsEntity class, a validateSave event has been implemented for the margin attribute. The user can’t save a product with a margin lower than 50%. In this case, an error is returned to stop the save action and give feedback to the end user.
// ProductsEntity class
//
// validateSave event at attribute level
Function event validateSave margin($event : Object) : Object
var $result : Object
//The user can't create a product whose margin is < 50%
If (This.margin<50)
$result:={errCode: 1; message: "The validation of this product failed"; \
extraDescription: {info: "The margin of this product ("+String(This.margin)+") is lower than 50%"}; seriousError: False}
End if
return $result
The same logic applies for a validateDrop event. For example, you can prevent a user from dropping a product whose status is not “To delete”.
the saving / dropping events
These events are triggered precisely during the save or drop action and can return errors if something goes wrong. They can be implemented:
- At the attribute level: to monitor changes to specific attribute values
- At the entity level: to check the entity content globally or compare attributes
Example:
In this example, in the ProductsEntity class, a saving event has been implemented for the userManualPath attribute. When a product is saved, the user manual document is created on the disk and its path is stored in the userManualPath attribute. Those two actions must be consistent.
If there is an error while creating the user manual document on the disk, an error is returned to give feedback and to be proceeded in the next event (afterSave) detailed below.
Note the content of the file is generated previously outside the saving event because it can be time consuming.
// ProductsEntity class
// saving event at attribute level
Function event saving userManualPath($event : Object) : Object
var $result : Object
var $userManualFile : 4D.File
var $fileCreated : Boolean
If (This.userManualPath#"")
$userManualFile:=File(This.userManualPath)
// The user manual document file is created on the disk
// This may fail if no more space is available
Try
// The content of the file has been generated and stored in a map in Storage.docMap previously
$docInfo:=Storage.docMap.query("name = :1"; This.name).first()
$userManualFile.setContent($docInfo.content)
Catch
// E.g.: No more space on disk
$result:={errCode: 1; message: "Error during the save action for this product"; extraDescription: {info: "There is no available space on disk to store the user manual"}}
End try
End if
return $result
The same logic applies for a dropping event. For example, you can drop the user manual document while dropping the product.
the after events
These events are triggered after the Save or Drop action. Because the action is finished, they can’t return errors. Their main purpose is to trigger business logic if a failure occurred during earlier events.
If no error occurred, they can also handle post-success logic — for example, sending a confirmation email.
These events are implemented at entity level only.
Example:
In this example, in the ProductsEntity class, an afterSave event has been implemented. If the userManualPath attribute has not been properly saved previously, its value is reseted to be consistent with the missing user manual document on the disk.
// ProductsEntity class
Function event afterSave($event : Object)
If (($event.status.success=False) && ($event.status.errors=Null)) // $event.status.errors is filled if the error comes from the validateSave event
// The userManualPath attribute has not been properly saved
// Its value is reset
If ($event.savedAttributes.indexOf("userManualPath")=-1)
This.userManualPath:=""
This.status:="KO"
End if
End if
The same logic applies for an afterDrop event— e.g., when dropping a product fails, you can log the incident.
Note long operations must be avoided as far as possible in events. If possible, such operations must be implemented elsewhere.
Do not wait to implement events to rely on a strong data integrity.
👉 Download the HDI to dive into the implementation details of ORDA events and watch the video below to have more details about running the HDI.
Add the EN video link
