In this section, we’ll explore the use of the Process API within the framework of intent-driven development, focusing on the implementation through JSON schemas.
Base schema structure
{
"id": "schema_id",
"main": {
"steps": {
"start": {
"type": "",
"action": "",
"args": {}
}
}
}
}
Every schema is required to have an ‘id’ property. This serves as its identifier in the schema registry. When calling a process from a different schema, you specify the schema in which the process is defined, based on this schema id.
Typically, a schema outlines an executable process. The “main” object represents the standard definition for this executable process. In the case of creating a library schema, you can omit the “main” object, as a default execution point isn’t necessary. However, for non-library schemas, having at least a “main” process is essential.
In a schema, you have the option to include multiple processes. Each property at the root level, with the exception of “id” and “sequences”, is treated as a separate process definition. This arrangement enables the division of complex processes into smaller, more manageable components, where the “main” process acts as the central execution point and the additional processes serve as utility functions within the main process.
Each process is equipped with a “steps” property. While there are additional properties that can be defined within a process, we’ll delve into those later. For the moment, it’s important to recognize that a “steps” property is essential, and it should be structured as a dictionary of named process steps. Each entry in this dictionary represents a step, and the name of the property is used to reference and execute that specific step. For instance, if there’s a step named “log” within the steps, that is the name you would use to initiate that step. The steps property should invariably contain a “start” object, which dictates the initial process step to be executed in the process.
{
"id": "schema_id",
"main": {
"steps": {
"start": {
"type": "math",
"action": "add",
"args": {
"value1": 10,
"value2": 20,
"target": "$data.sum"
},
"next_step": "log"
},
"log": {
"type": "console",
"action": "log",
"args": {
"messages": ["The sum equals: ", "$data.sum"]
}
}
}
}
}
This process has two steps:
- start – Executes a math addition action and stores the result in the ‘sum’ property of the process data.
- log – Outputs a message to the console, comprising a composite string that begins with a label text and is followed by the result from the ‘start’ step.
Be aware that the ‘start’ step includes a property named “next_step”. This property specifies the subsequent step to be executed in the process, which in this instance is the “log” step. Since the ‘log’ step lacks a “next_step” property, it signifies the end of the process. When the process runner completes the ‘log’ step and notes the absence of a next step, it will conclude the process and return the result.
Sequences
For enhanced maintainability and reusability, it can be beneficial to divide a larger process into smaller, more manageable processes. In such cases, you might have a series of processes that need to be executed sequentially. This requires running each process from the beginning to the end of the sequence, ensuring that each current process is fully completed before initiating the next one.
{
"id": "schema_id",
"sequences": [ "process_1", "process_2", "process_3" ]
"process_1": {
"steps": {
"start": { ... step details ... }
}
},
"process_2": {
"steps": {
"start": { ... step details ... }
}
},
"process_3": {
"steps": {
"start": { ... step details ... }
}
}
}
It’s important to observe that in the example given above, there is no “main” process. This is because the “sequences” have become the default execution point in this setup.
Process parameters
Utility processes often necessitate external inputs for performing their actions. Take, for instance, a utility process designed to save a model to a remote server. To function effectively, this process requires specific information, such as the entity name and the model to be saved. Prior to running, the process must verify that all necessary parameters are accurately established. This is where the significance of defining parameters comes into play.
{
"id": "remote_crud",
"save": {
"parameters_def": {
"entity": { "type": "string", "required": true },
"model": { "type": "object", "required": true }
},
"steps": {
"start": { ... perform save ... }
}
}
}
The ‘save’ process features a “parameters_def” property in addition to the mandatory “steps” property. This “parameters_def” outlines the necessary parameters for the process’s operation. When invoking this process, the parameters supplied to it will be validated against this definition. Consequently, the execution process (which serves as the ‘parameter’ parameter in the module action) will be equipped with a “parameters” object containing the requisite values.
Now lets look at how I would call the above process and pass the parameters to the process for processing.
{
"id": "person_crud",
"main": {
"steps": {
"start": {
"type": "process",
"action": "save",
"args": {
"schema": "remote_crud",
"parameters": {
"entity": "person",
"model": {
"fistName": "John",
"lastName": "Doe",
"age": 20
}
}
}
}
}
}
}
In this scenario, we utilize the process intent type to invoke other IDD processes. While typically the action corresponds to the one specified in the module, here the action denotes the name of the process to be executed. Next, it’s essential to specify the schema on which this process is defined. Following conventional practices, all supplementary details are included in the ‘args’ object. Hence, in this situation, both the schema and parameters are defined within ‘args’.
In the executing action we can access these parameter values with the expression starting with “$parameters“.
Binding engine context id
In the realm of Intent-Driven Development (IDD), there are situations where you might need to execute actions on the binding context. To facilitate this, it’s necessary to transmit the binding context ID. This is achieved through the use of process parameters, adhering to a specific convention.
parameters_def: {
bId: { type: "number", required: true }
}
Use this parameter as the context id for binding operations.
Binding pre and post operations
On each step you can have these additional binding properties:
- binding_before – Assign values to the binding context before initiating this process step.
- binding_after – Update binding context values following the execution of the step.
For this functionality to operate, the ‘bId‘ parameter must be included in the process parameters. This approach is more commonly utilized in schema process execution, rather than from a JavaScript perspective.
Both ‘binding_before
‘ and ‘binding_after
‘ are structured as dictionaries. In these dictionaries, the key represents the path within the binding context where the value should be set. The dictionary’s value can either be the actual value to set or a path expression as defined by the process API.
{
"id": "test",
"main": {
"parameters_def": {
"bId": { "type": "number", "required": true },
"busyColor": { "type": "string", "required": true },
"doneColor": { "type": "string", "required": true }
},
"steps": {
"start": {
"binding_before": {
"status.value": "busy",
"color": "$parameters.busyColor"
},
"type": " ... type ...",
"action": " ... action ... ",
"args": { ... args ... },
"binding_after": {
"status.value": "done",
"color": "$parameters.doneColor"
}
}
}
}
}