diff --git a/examples/README.md b/examples/README.md index 85f83fdd..83213c7e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -26,6 +26,7 @@ Provides Serverless Workflow language examples - [Accumulate room readings and create timely reports (ExecTimeout and KeepActive)](#Accumulate-room-readings) - [Car vitals checks (SubFlow state Repeat)](#Car-Vitals-Checks) - [Book Lending Workflow](#Book-Lending) +- [Filling a glass of water (Expression functions)](#Filling-a-glass-of-water) ### Hello World Example @@ -3753,3 +3754,122 @@ events: file://books/lending/events.json + +### Filling a glass of water + +#### Description + +In this example we showcase the power of [expression functions](../specification.md#Using-Functions-For-Expression-Evaluation). +Our workflow definition is assumed to have the following data input: + +```json +{ + "counts": { + "current": 0, + "max": 10 + } +} +``` + +Our workflow simulates filling up a glass of water one "count" at a time until "max" count is reached which +represents our glass is full. +Each time we increment the current count, the workflow checks if we need to keep refilling the glass. +If the current count reaches the max count, the workflow execution ends. +To increment the current count, the workflow invokes the "IncrementCurrent" expression function. +Its results are then merged back into the state data according to the "toStateData" property of the event data filter. + +#### Workflow Diagram + +

+Fill Glass of Water Example +

+ +#### Workflow Definition + + + + + + + + + + +
JSONYAML
+ +```json +{ + "id": "fillgrassofwater", + "name": "Fill glass of water workflow", + "start": "Check if full", + "functions": [ + { + "name": "Increment Current Count Function", + "type": "expression", + "operation": ".counts.current += 1 | .counts.current" + } + ], + "states": [ + { + "name": "Check if full", + "type": "switch", + "dataConditions": [ + { + "name": "Need to fill more", + "condition": "${ .counts.current < .counts.max }", + "transition": "Add Water" + }, + { + "name": "Glass full", + "condition": ".counts.current >= .counts.max", + "end": true + } + ] + }, + { + "name": "Add Water", + "type": "operation", + "actions": [ + { + "functionRef": "Increment Current Count Function", + "actionDataFilter": { + "toStateData": ".counts.current" + } + } + ], + "transition": "Check If Full" + } + ] +} +``` + + + +```yaml +id: fillgrassofwater +name: Fill glass of water workflow +start: Check if full +functions: +- name: Increment Current Count Function + type: expression + operation: ".counts.current += 1 | .counts.current" +states: +- name: Check if full + type: switch + dataConditions: + - name: Need to fill more + condition: "${ .counts.current < .counts.max }" + transition: Add Water + - name: Glass full + condition: ".counts.current >= .counts.max" + end: true +- name: Add Water + type: operation + actions: + - functionRef: Increment Current Count Function + actionDataFilter: + toStateData: ".counts.current" + transition: Check If Full +``` + +
diff --git a/media/examples/examples-fill-glass.png b/media/examples/examples-fill-glass.png new file mode 100644 index 00000000..bc061e2e Binary files /dev/null and b/media/examples/examples-fill-glass.png differ diff --git a/specification.md b/specification.md index c1990ee0..0f24e0d8 100644 --- a/specification.md +++ b/specification.md @@ -1081,10 +1081,9 @@ In addition to defining RESTful and RPC services and their operations, workflow can also be used to define expressions that should be evaluated during workflow execution. Defining expressions as part of function definitions has the benefit of being able to reference -them by their logical name through workflow states where expression evaluation is required, thus making them -reusable definitions. +them by their logical name through workflow states where expression evaluation is required. -Expression expression functions must declare their `type` parameter to be `expression`. +Expression functions must declare their `type` parameter to be `expression`. Let's take at an example of such definitions: @@ -1106,9 +1105,9 @@ Let's take at an example of such definitions: ``` Here we define two reusable expression functions. Expressions in Serverless Workflow -are evaluated against the workflow data. Note that different data filters play a big role as to which parts of the -workflow data are selected. Reference the -[State Data Filtering](#State-data-filters) section for more information on this. +can be evaluated against the workflow, or workflow state data. Note that different data filters play a big role as to which parts of the +workflow data are being evaluated by the expressions. Reference the +[State Data Filtering](#State-Data-Filtering) section for more information on this. Our expression function definitions can now be referenced by workflow states when they need to be evaluated. For example: @@ -1138,6 +1137,61 @@ Our expression function definitions can now be referenced by workflow states whe } ``` +Our expression functions can also be referenced and executed as part of state [action](#Action-Definition) execution. +Let's say we have the following workflow definition: + +```json +{ + "name": "simpleadd", + "functions": [ + { + "name": "Increment Count Function", + "type": "expression", + "operation": ".count += 1 | .count" + } + ], + "start": "Initialize Count", + "states": [ + { + "name": "Initialize Count", + "type": "inject", + "data": { + "count": 0 + }, + "transition": "Increment Count" + }, + { + "name": "Increment Count", + "type": "operation", + "actions": [ + { + "functionRef": "Increment Count Function", + "actionFilter": { + "toStateData": "${ .count }" + } + } + ], + "end": true + } + ] +} +``` + +The starting [inject state](#Inject-State) "Initialize Count" injects the count element into our state data, +which then becomes the state data input of our "Increment Count" [operation state](#Operation-State). +This state defines an invocation of the "Increment Count Function" expression function defined in our workflow definition. + +This triggers the evaluation of the defined expression. The input of this expression is by default the current state data. +Just like with "rest", and "rpc" type functions, expression functions also produce a result. In this case +the result of the expression is just the number 1. +The actions filter then assigns this result to the state data element "count" and the state data becomes: + +``` json +{ + "count": 1 +} +``` + Note that the used function definition type in this case must be `expression`. For more information about functions, reference the [Functions definitions](#Function-Definition) section.