Context refers to a shared data set between different parts of a system.
This shared data ensures seamless communication and operation across various components, enabling efficient data handling and decision-making processes.
In case of Kaa Rule Engine, these components are “endpoint”, “trigger”, “rule”, and “action”.
The context is crucial for ensuring smooth transitions and data sharing between these components. Without it, each component would operate in isolation.
By using context, we can pass additional data and the state of one component to the next.
Although the structure of the context may seem complex, this article will explain it step by step.
To fully grasp the concept of context, we need to address a few key points:
Jump on board, and let’s dive in!
First, let’s examine the Rule Engine execution flow to understand why context is such an integral part of it.
When we create our rule, we attach it to the “trigger” and “action”, specify related “endpoints”, and let the “Rule engine” do its job. When “trigger” executes, “Rule Engine” can create a context for the rule execution, inject it into the execution workflow, and run the rule.
As you can see in the diagram, the “context” is isolated for each “rule” execution. It means that each rule has its own context, and the objects are not shared between rules. But for the “action” execution, the context is the same as for the “rule” that performed it.
At the “Endpoint Scope”, triggers respond to events from specific endpoints. Rules execute additional logic and perform actions directly related to the endpoint. The injected context contains additional endpoint and event-specific data.
At the “Application Version Scope”, triggers execute rules for each endpoint registered in a particular application version. All rules execute the same logic, and actions are performed for each endpoint belonging to the application version. Contexts are not shared between rules. All next steps are similar to the Endpoint level execution.
The same as the “Application Version Scope” execution, but the rules execute for all endpoints registered in the application.
For scheduled tasks, triggers execute rules based on a fixed schedule, independent of endpoint events. These rules perform predefined actions. Unlike endpoint, application version, and application level execution, context does not contain any event-specific data, as there are no events involved.
When the “trigger” receives an event from the attached endpoint, the “context” is created. It does not exist forever. It will be destroyed after the “rule” and “action” execute. We have a few scenarios of how the context is created.
In this section, we will describe the context lifecycle when the “trigger” that is attached to the “endpoint”, “application”, or “application version” is executed. The diagrams below describe the context lifecycle in a high-level view and skip steps not involved in a context perspective to reduce overall complexity.
Note: The Common Context is not an official term. It is used to describe the context part that is not related to the endpoint or specific trigger. It contains “toolbox” and “data” objects.
In this section, we will describe the context lifecycle when the Scheduled (cron) “trigger” is executed.
The main difference between the first and second flow is that the “Event-based context lifecycle” has additional steps for creating endpoint and trigger context objects. But when the scheduled trigger is invoked, no endpoints are attached, so there is no source to create endpoints and trigger context objects from.
The “ctx” object is available in JavaScript expressions and serves as a comprehensive toolkit for various operations. It contains the following objects:
Structure:
Let’s take a closer look at each of these objects.
This context object varies based on the invoked trigger, providing relevant data structures for different scenarios. It is used to store information about the event that triggered the rule.
Let’s take a look at the trigger context object structure.
When you write scripts, only one of the “trigger” objects is available in the context, based on the trigger type. Each of them has a boolean property to check if the trigger is of the type that we expect.
Let’s zoom in to the diagram parts to look at the structure of each possible “trigger” object.
JSON representation:
{
"isEndpointMetadataUpdated": true,
"endpointMetadataUpdated": {
"endpointId": "{endpointId}",
"appVersionName": "{appVersionName}",
"added": "{added}",
"removed": "{removed}",
"updated": "{updated}"
}
}
This object provides details on metadata changes for an endpoint, including additions, removals, and updates.
Example:
// Get the endpoint ID from the endpoint metadata updated context
let epID = ctx.trigger.endpointMetadataUpdated.endpointId;
console.log(`Metadata updated in endpoint[${epID}]`);
// Handle the changes in a metadata
let added = ctx.trigger.endpointMetadataUpdated.added;
let removed = ctx.trigger.endpointMetadataUpdated.removed;
let updated = ctx.trigger.endpointMetadataUpdated.updated;
// Perform actions based on the changes
if (added) {
// ... Perform actions for added metadata
console.log("Added metadata:", added);
} else if (removed) {
// ... Perform actions for removed metadata
console.log("Removed metadata:", removed);
} else if (updated) {
// ... Perform actions for updated metadata
console.log("Updated metadata:", updated);
} else {
// ... Handle other cases
console.log("No changes in metadata");
}
JSON representation:
{
"isEndpointDataSamplesReceived": true,
"endpointDataSamplesReceived": {
"endpointId": "{endpointId}",
"appVersionName": "{appVersionName}",
"dataSamples": "{dataSamples}"
}
}
This object delivers information about new data samples received from an endpoint, helping to monitor real-time data flows.
Example:
// Get the endpoint ID from the endpoint data samples received context
let epID = ctx.trigger.endpointDataSamplesReceived.endpointId;
console.log(`Endpoint[${epId}] received data samples`);
// Handle the received data samples
let dataSamples = ctx.trigger.endpointDataSamplesReceived.dataSamples;
// Process the data samples
for (let sample of dataSamples) {
// ... Perform actions with the data sample
console.log(`Received data sample: ${sample}`);
}
Note: The “{timeSeriesName}” property key in the “EndpointTimeSeriesUpdated” object is replaced with an actual time series name during rule execution.
JSON representation:
{
"isEndpointTimeSeriesUpdated": true,
"endpointTimeSeriesUpdated": {
"endpointId": "{endpointId}",
"appVersionName": "{appVersionName}",
"timeSeriesName": "{timeSeriesName}",
"{timeSeriesName}": "{valuesByTimestamp}"
}
}
Time series updates are captured in this object, providing structured data linked to timestamps for historical analysis.
Example:
// Get the endpoint ID from the endpoint time series updated context
let epID = ctx.trigger.endpointTimeSeriesUpdated.endpointId;
console.log(`Endpoint[${epID}] was updated`);
// Handle the time series updates
let timeSeriesName = ctx.trigger.endpointTimeSeriesUpdated.timeSeriesName;
let valuesByTimestamp = ctx.trigger.endpointTimeSeriesUpdated[timeSeriesName];
console.log(`Updated timeseries: ${valuesByTimestamp}`);
// Process the time series data
for (let timestamp in valuesByTimestamp) {
// ... Perform actions with the time series data
console.log(`Time series data for ${timeSeriesName} at ${timestamp}: ${valuesByTimestamp[timestamp]}`);
}
JSON representation:
{
"isCommandDispatched": true,
"commandDispatched": {
"endpointId": "{endpointId}",
"appVersionName": "{appVersionName}",
"commandType": "{commandType}",
"commands": "{commands}"
}
}
Commands dispatched to an endpoint are detailed in this object, enabling task tracking and management.
Example:
// Get the endpoint ID from the endpoint command dispatched context
let epID = ctx.trigger.commandDispatched.endpointId;
console.log(`Command dispatched to endpoint[${epID}]`);
// Handle the dispatched commands
let commandType = ctx.trigger.commandDispatched.commandType;
console.log(`Dispatched command type: ${commandType}`);
let commands = ctx.trigger.commandDispatched.commands;
// Process the dispatched commands
for (command of commands) {
// ... Perform actions with the command
console.log(`Dispatched command: ${command}`);
}
JSON representation:
{
"isCommandResult": true,
"commandResult": {
"endpointId": "{endpointId}",
"appVersionName": "{appVersionName}",
"commandType": "{commandType}",
"results": "{results}"
}
}
This object contains the outcomes of executed commands, providing insights into the execution status and results.
Example:
// Get the endpoint ID from the endpoint command result received context
let epID = ctx.trigger.commandResult.endpointId;
console.log(`Command result received for endpoint[${epID}]`);
// Handle the command results
let commandType = ctx.trigger.commandResult.commandType;
console.log(`Command result type: ${commandType}`);
let results = ctx.trigger.commandResult.results;
// Process the command results
for (result of results) {
// ... Perform actions with the command result
console.log(`Command result: ${result}`);
}
{
"isAlertLifecycleEvent": true,
"alertLifecycleEvent": {
"alertId": "{alertId}",
"alertType": "{alertType}",
"severityLevel": "{severityLevel}",
"eventType": "{eventType}",
"timestamp": "{timestamp}",
"metadata": "{metadata}",
"systemMetadata": "{systemMetadata}"
}
}
Alert lifecycle events are captured in this object, detailing the alert’s type, severity, and associated metadata.
Example:
// Get the alert ID from the alert lifecycle event context
let alertID = ctx.trigger.alertLifecycleEvent.alertId;
// Handle the alert lifecycle event
let alertType = ctx.trigger.alertLifecycleEvent.alertType;
console.log(`Alert type: ${alertType}`);
let severityLevel = ctx.trigger.alertLifecycleEvent.severityLevel;
console.log(`Alert severity level: ${severityLevel}`);
let eventType = ctx.trigger.alertLifecycleEvent.eventType;
console.log(`Alert event type: ${eventType}`);
let timestamp = ctx.trigger.alertLifecycleEvent.timestamp;
console.log(`Alert timestamp: ${timestamp}`);
let metadata = ctx.trigger.alertLifecycleEvent.metadata;
console.log(`Alert metadata: ${metadata}`);
let systemMetadata = ctx.trigger.alertLifecycleEvent.systemMetadata;
console.log(`Alert system metadata: ${systemMetadata}`);
// Process the alert lifecycle event based on severity level and event type
if (severityLevel === "CRITICAL" && eventType === "CREATED") {
// ... Perform actions for critical alerts
console.log(`Critical alert created: ${alertID}`);
}
Data is basically a mutable empty object that can be used to store data that is passed between the rule and action. It has no predefined structure.
Example:
ctx.trigger.data = {
"key1": true,
"key2": 0,
"key3": "value"
};
When a rule is executed for a particular endpoint, the “endpoint” object is created and passed to the rule context. The “endpoint” object contains information about the endpoint, such as its ID, metadata, and associated application and application version. It also provides methods to retrieve time series data, analytics aggregations, and relations.
As you can see, the “endpoint” is a rich object that provides access to a huge amount of endpoint-related data.
From the above diagram, you can see the “endpoint” methods to retrieve time series data, analytics aggregations, and relations.
Let’s take a closer look at the objects that can be retrieved from the “endpoint”.
The application to which the endpoint belongs.
It contains the following methods:
The application version to which the endpoint belongs.
It contains the following methods:
The time series data produced by the endpoint.
It contains the following methods:
The analytics aggregations for the endpoint.
It contains the following methods:
The relations for the endpoint.
It contains the following methods:
A toolbox is a collection of utility functions that can be used to perform various tasks.
More about Toolbox Objects:
In the previous section, we skipped the “Schedule” trigger.
This is because the complex “trigger” object is not available for this trigger.
Only ctx.trigger.isCron
boolean flag is present.
The context object structure in this case looks as displayed in Context Structure main diagram.
For those of you who have already read the Triggers article, you know that the Schedule trigger could have no relation to any endpoint. Consequently, the “endpoint” object of context is unavailable for the Schedule trigger in this type of setup.
The context object structure in this case looks like this:
The absence of endpoint-related context in such cases underscores the need for adaptable handling mechanisms in your rule engine workflows. For instance, tasks executed on a fixed schedule often rely on predefined data or external resources rather than dynamic endpoint data. Understanding such nuances helps in designing effective and flexible systems.
That was a long ride, but we made it! We took a deep dive into the context structure, scope, and lifetime. We explored the context object in detail. We discussed the different types of objects that are placed in context and how they can be created and used. We also discussed the different types of triggers and how the type of trigger affects the context structure.
Now you know almost everything about the context. If you have any questions, please feel free to ask us.