Link Search Menu Expand Document

Conditional Transitions

Oftentimes a workflow needs to be a little bit smarter than an immutable sequence of states. That’s when you might need conditional transitions. In this article you’ll learn about instance input data, the Switch State, and how to define loops.

Demo

id: multiposter
functions:
- id: httprequest
  image: vorteil/request:v2
  type: reusable
states:
- id: ifelse
  type: switch
  conditions:
  - condition: jq(.names)
    transition: poster
- id: poster
  type: action
  action:
    function: httprequest
    input: '{
      "method": "POST",
      "url": "https://jsonplaceholder.typicode.com/posts",
      "body": {
        "name": .names[0]
      }
    }'
  transform: jq(del(.names[0]))
  transition: ifelse

Input

input:
  names: 
    - "Alan"
    - "Jon"
    - "Trent"

Output

{
  "names": [],
  "return": {
		"id": 101
	}
}

Instance Input

Workflows can be invoked with input data that will be available as instance data. There are a few ifs-and-buts that apply to input data, but it’s not that complicated.

Input JSON Object

If the input is a JSON object it will become the instance data.

Input Data

{
  "key": "value"
}

Instance Data

{
  "key": "value"
}

Input JSON Non-Object

If the input is valid JSON but not an object it will be stored under "input".

Input Data

[1, 2, 3]

Instance Data

{
  "input": [1, 2, 3]
}

Input Non-JSON

Finally, if the input is not valid JSON it will be base64 encoded into a string and then stored under "input".

Hello, world!

Instance Data

{
  "input": "SGVsbG8sIHdvcmxkIQ=="
}

Switch State

The Switch State can make decisions about where to transition to next based on the instance data by evaluating a number of jq expressions and checking the results. Here’s an example switch state definition:

- id: ifelse
  type: switch
  conditions:
  - condition: 'jq(.person.age > 18)'
    transition: accept
    #transform:
  - condition: 'jq(.person.age != nil)'
    transition: reject
    #transform:
  defaultTransition: failure
  #defaultTransform:

Each of the conditions will be evaluated in the order it appears by running the jq command in condition. Any result other than null, false, {}, [], "", or 0 will cause the condition to be considered a successful match. If no conditions match the default transition will be used.

In the demo example the switch state will transition to poster until the list of names is empty, at which point the workflow will end.

Other Conditional Transitions

The Switch State is not the only way to do conditional transitions. The EventsXor state also transitions conditionally based on which CloudEvent was received. All states can also define handlers for catching various types of errors. Both of these will be discussed in a later article.

Loops

By transitioning to a state that has already happened it’s possible to create loops in workflow instances. In this demo we’ve got a type of range loop, iterating over the contents of an array. Direktiv sets limits for the number of transitions an instance can make in order to protect itself from infinitely-looping workflows. Consider some of the following alternatives if you want to have a loop:

Foreach State

For range loops like the one in this demo there’s another state called a Foreach State that simplifies the logic and splits up a data set to run many actions in parallel without doing lots of transitions. A fuller explanation of the Foreach State will be discussed in another article, but here’s an equivalent workflow definition to the demo if you’re curious:

id: multiposter
functions:
- id: httprequest
  image: vorteil/request:v2
  type: reusable
states:
- id: poster
  type: foreach
  array: 'jq(.names[] | { name: . })'
  action:
    function: httprequest
    input: 
      method: "POST"
      url: "https://jsonplaceholder.typicode.com/posts"
      body:
        name: 'jq(.)'
  transform: 'jq(del(.names) | .names = [])'

Retries

One obvious use for loops is to retry some logic if an error occurs, but there’s no need to design looping workflow because Direktiv has configurable error catching & retrying available on every action-based state. This will be discussed in a later article.

Isolates

For large data sets or logic that could needs to loop many times it’s generally better to custom-write an Isolate function that performs all of the computation. Writing custom functions is discussed in another article.


Copyright © 2021 Vorteil.io.