Gravio actions are flows composed of multiple connected components that define a sequence of data processing. Each component operates in parallel in its own thread, retrieving input Payload from its queue, processing it, generating output Payload, and posting it to the next component’s queue.
The action execution model progresses as follows:
This sequence can be understood as a bucket relay where data is passed along. However, in reality, the same Payload isn’t passed along; each component generates a new Payload after applying its own processing.
In a simple action flow where components are arranged in series, it can be represented by the following flow diagram:
C01 -> C02 -> C03 -> [END]
In this case, the components operate as follows:
While each component operates in parallel in its own thread, the order is maintained by Payload dependencies, making it appear as if C01, C02, and C03 are operating in sequence.
In traditional action flows, components were connected in series, and it might not have appeared that they were operating in parallel threads. However, in reality, while one component was waiting for I/O, other components could operate, making efficient use of CPU resources across the entire action.
In the new version, explicit branching can be introduced into action flows. For example, using the Parallel component, you can clone one input Payload and distribute it to multiple processing lines:
+-> C11 -> C12
|
C01 -> Parallel -> C03 -> [END]
In this example, C01’s output Payload is cloned by the Parallel component and posted to both C03’s and C11’s queues. This allows the C03 processing line and the C11→C12 processing line to execute in parallel.
In parallel processing, we need to decide how to handle the output Payload from the C11 line. In the current implementation, the output Payload from the C11 line is discarded at the end, and the output Payload from the C03 line reaches the action’s END (final output). This prevents system behavior from becoming ambiguous when multiple Payloads reach [END].
Branch components allow you to transfer Payloads to different components based on conditions. Here’s a basic example of branching:
C01 -> If -> C02 -> [END]
|
+-----------> C03
In this case, if the If component’s condition expression is true, the output Payload is transferred to C02; if false, it’s transferred to C03. Using branching allows for efficient implementation of condition-based processing.
The If component is used to implement conditional branching.
Jump/JumpIf components provide functionality to call other actions.
Call/CallIf components also call other actions but, unlike Jump, control returns to the caller.
The choice between Call/Jump and CallIf/JumpIf depends on the flow of processing. Using Call/CallIf connects to C03 after C12, while using Jump/JumpIf connects to a virtual [END] after C12, making the output Payload the action’s output.
+-> C11 -> C12
|
C01 -> C02 -> C03 -> [END]
The Parallel component is used to clone input Payload and transfer it to multiple processing paths.
When actions are instantiated from the same branch component, if they have the same ActionId, the same action instance is used. This ensures that when the same action is called from different paths, the action’s state is shared.
For example, in the following case, even when the same action with ActionId(A1) is called from two branches, processing remains organized:
(A1)
+-> C11 -> C12
| (A1)
+-> C11 -> C12
|
Split -> Call -> C03 -> [END]
When multiple output Payloads reach a branch component, the action is instantiated when the first Payload arrives, and the same action instance is used for subsequent arrivals.
Different branch components generate separate action instances even with the same ActionId. This allows the same action to be executed independently in different contexts:
(A2)
+-> C21 -> C22
| (A1)
+-> C11 -> C12
| (A1)
| +-> C11 -> C12
|
Split -> Jump -> Call -> [END]
This way, you can call the same action from different branch components when you want to use it in different contexts. You can also use the same action with Call in one place and Jump in another.
When you want to call different actions after components that post multiple output Payloads (like Split or CSVRead/SensorDataDB), you can use patterns like this:
(A1)
+-> C11 -> C12
|
| (A2)
| +-> C21 -> C22
|
Split -> JumpIf -> JumpIf -> C03 -> [END]
By using JumpIf components to call different actions based on conditions, you can implement control flow similar to a programming language’s switch statement. You can also achieve similar control with a single component by changing the ActionId in PreMappings based on condition evaluation.
When you want to execute different actions based on conditions, you can utilize JumpIf components:
(A1)
+-> C11 -> C12
|
| (A2)
| +-> C21 -> C22
|
Split -> JumpIf -> JumpIf -> C03 -> [END]
By setting conditions in each JumpIf component’s Expr property and specifying the desired ActionId, you can implement processing branches for various conditions.
With C02 as a branch component, you can either post directly to C03 or to C11 of another action based on conditions:
+-> C11 -> C12
|
C01 -> C02 -> C03 -> [END]
In this case, you use flow control components like Jump or Call. Using Jump connects C12 to a virtual [END], while using Call connects C12 to C03.
Posting to components in other actions doesn’t stop the operation of the original action. All components operate in parallel, with thread scheduling dependent on the Go language runtime. For example, the first Payload goes to (A1) and ultimately to the virtual [END], while the second Payload goes to (A2).
By combining branching and inter-action coordination, you can efficiently implement complex business logic. You can improve overall processing efficiency by leveraging parallel processing characteristics while resolving bottlenecks caused by I/O waiting and other factors.