本文介绍了输入和输出的基本知识,您可以参见本文示例进行操作。

流程和步骤

通常流程的多个步骤之间需要传递数据。和函数式编程语言类似,FDL的步骤类似于函数,它接受输入(Input),并返回输出(Output),输出会保存在父步骤(调用者)的本地(Local)变量里。其中,输入和输出的类型必须是JSON对象格式,本地变量的类型因步骤而异。例如任务步骤把调用函数计算函数的返回结果作为本地变量,并行步骤把它所有分支的输出(数组)作为本地变量。步骤的输入、输出和本地变量总大小不能超过32 KiB,否则会导致流程执行失败。

如果一个步骤包含另一个步骤,则称外层步骤为父步骤,被包含步骤为子步骤。最外层步骤的父步骤是流程。如果两个步骤的父步骤相同,则这两个步骤是同级步骤。

流程和步骤都有输入、输出和本地变量。他们的转换关系如下:

  • 步骤输入映射(inputMappings)将父步骤的输入和本地变量映射为子步骤的输入。
  • 步骤输出映射(outputMappings)将当前步骤的输入和本地变量映射为当前步骤的输出。
  • 流程输入映射(inputMappings)将用户执行流程时的输入映射为流程的输入。
  • 流程输出映射(outputMappings)将流程输入和本地变量映射为流程的输出。

一个父步骤的本地变量保存了其所有子步骤输出的合并,如果输出中有同名键值,则后执行步骤会覆盖前执行步骤的结果。在大多数情况下,您不需要指定输入和输出映射,而使用默认的映射行为。他们的转换关系如下:

  • 在不指定输入映射情况下,子步骤的输入是其父步骤输入和父步骤本地变量的合并(如果本地变量和输入有同名键值,则本地变量会覆盖输入)。
  • 在不指定输出映射情况下,除并行步骤和并行循环步骤外的其它步骤都会将本地变量作为输出。

如果您想更好地控制输入和输出,则需要了解详细的映射规则。

下面示例流程的输入输出映射如图所示。其中step1是step2和step3的父步骤。step1和step4是最外层步骤。

version: v1
type: flow
steps:
  - type: parallel
    name: step1
    branches:
      - steps:
        - type: pass
          name: step2
      - steps:
        - type: pass
          name: step3
  - type: pass
    name: step4      
flow-io-mappings-v0

为了便于理解,这些映射可以用下面的代码描述。

func flow(input0 Input) (output0 Output) {
  local0 := {}
  input1 := buildInput(step1InputMappings, input0, local0)
  output1 := step1(input1)
  save(local0, output1)
  input4 := buildInput(step4InputMappings, input0, local0)
  output4 := step4(input4)
  save(local0, output4)
  return buildOutput(flowOutputMappings, input0, local0)
}
func step1(input1 Input) (output1 Output) {
  local10 := {}
  input2 := buildInput(step2InputMappings, input1, local10)
  output2 := step2(input2)
  save(local10, output2)
  local11 := {}
  input3 := buildInput(step3InputMappings, input1, local11)
  output3 := step3(input3)
  save(local11, output3)
  return buildOutput(step1OutputMappings, [local10, local11])
}
func step2(input2 Input) (output2 Output) {
}
func step3(input3 Input) (output3 Output) {
}
func step4(input4 Input) (output4 Output) {
}       

在这个例子中,流程包含了两个子步骤:step1step4step1是一个并行步骤,它包含step2step3两个子步骤。

  1. 系统在开始执行流程时,根据流程的输入映射将StartExecution输入转换为流程输入(input0)。
  2. flow开始时,其local0为空。
  3. 系统根据step1的输入映射(step1InputMappings)来为它准备输入input1,映射的源来自flow的输入input0和本地变量local0
  4. 调用step1,传入输入input1step1返回输出output1
    • step1开始时,其local0为空 。因为step1是一个并行步骤,所以每个分支都对应一个本地变量,从而避免并发访问的问题。
    • 系统根据step2的输入映射(step2InputMappings)来为它准备输入input2,映射的源来自step1的输入input1和本地变量local10
    • 调用step2,传入输入input2step2返回输出output2
    • 系统将step2的输出保存到step1的本地变量local10
    • 同样的,系统调用step3并将结果保存在step1的本地变量local11
  5. 系统将step1的输出保存到flow的本地变量local0
  6. 类似的,系统根据step4的输入映射来为它准备输入input4,映射的源来自flow的输入input0和本地变量local0
    说明 这时的本地变量 local0可能已经包含了 step1的输出,从而实现了 step1step4的数据传递。
  7. 调用step4,传入输入input4step4返回输出output4
  8. 系统将step4的输出保存到flow的本地变量local0
  9. 最后,系统根据流程的输出映射将local0转换为流程输出。

类型定义

输入映射和输出映射都是由targetsource组成的数组类型,其中source定义了参数来源,根据不同的映射取值不同,例如$input.key表示参数来自输入input$.key所对应的值,target定义了目标参数名称。 当source$开始表示该值通过JSON Path方式指定(可以通过这个工具调试JSON Path),系统会根据该路径解析为具体值,否则认为该值是常量类型。

  • 来源(Source)

    来源支持使用常量作为source值,可以使用数字(number)、字符串(string)、布尔(boolean)、数组(array)、对象(object)或者null类型。

    下面示例映射的source使用了不同类型的常量,其产生的输出如下所示。

    outputMappings:
      - target: int_key
        source: 1
      - target: bool_key
        source: true
      - target: string_key
        source: abc
      - target: float_key
        source: 1.234
      - target: null_key
        source: null
      - target: array1
        source: [1, 2, 3]
      - target: array2
        source:
          - 1
          - 2
          - 3
      - target: object1
        source: {a: b}
      - target: object2
        source:
          a:           
    {
      "array1": [1, 2, 3],
      "array2": [1, 2, 3],
      "bool_key": true,
      "float_key": 1.234,
      "int_key": 1,
      "null_key": null,
      "object1": {
        "a": "b"
      },
      "object2": {
        "a": "b"
      },
      "string_key": "abc"
    }            
  • 目标

    目标只能是一个字符串类型常量。

输入映射

输入映射将父步骤的输入($input)、父步骤的本地变量($local)、或者常量转换为子步骤的输入。如果没有指定输入映射,父步骤的输入和父步骤的本地变量合并后会被当做子步骤的输入。如果父步骤的输入和父步骤的本地变量中有相同名称,则新的输入中采用本地变量中的名或值。

inputMappings:
  - target: key1
    source: $input.key1
  - target: key2
    source: $local.key2
  - target: key3
    source: literal         
输入 $input 本地变量 $local 输入映射 子步骤输入
{
"key1":"value1"
}
{
"key2":"value2"
}
inputMappings:
  - target: key1
    source: $input.key1
  - target: key2
    source: $local.key2
  - target: key3
    source: literal
{
"key1":"value1"
"key2":"value2"
"key3":"literal"
}
{
"key1":"value1"
}
{
"key2":"value2"
}
{
"key1":"value1"
"key2":"value2"
}
{
"key1":"value1"
}
{
"key1":"value2"
}
{
"key1":"value2"
}

输出映射

输出映射将当前步骤的输入($input)、本地变量($local)、或者常量转换为本地步骤的输出。如果没有指定输出映射,对于选择步骤和循环步骤,它们的本地变量会被当做其输出,任务步骤会将具体任务执行结果作为输出。由于并行(Parallel)和并行循环(ForEach)步骤的本地变量是数组类型,所以您需要定义输出映射将数组转换结果成JSON对象格式,默认不会输出它们的本地变量。具体介绍可参见步骤描述。

outputMappings:
  - target: key1
    source: $input.key1
  - target: key2
    source: $local.key2
  - target: key3
    source: literal          
输入 $input 本地变量 $local 输出映射 步骤输出
{
"key1":"value1"
}
{
"key2":"value2"
}
outputMappings:
  - target: key1
    source: $input.key1
  - target: key2
    source: $local.key2
  - target: key3
    source: literal
{
"key1":"value1"
"key2":"value2"
"key3":"literal"
}
{
"key1":"value1"
}
[
  {
    ”key2”:”value2.1”
  },
  {
    ”key2”:”value2.2”
  }
]
outputMappings:
  - target: key1
    source: $input.key1
  - target: key2
    source: $local[*].key2
  - target: key3
    source: literal
{
  ”key1”:”value1”,
  ”key2”:[“value2.1”,”value2.2”],
  ”key3”:”literal”
}
{
"key1":"value1"
}
{
"key2":"value2"
}
{
"key2":"value2"
}

输出如何保存到父步骤本地变量

子步骤的输出($output)会被并入父步骤的本地变量。如果二者有重复名称,则输出中的名或值会覆盖本地变量相应名或值。

输出 $output 父步骤本地变量 $local 更改后父步骤本地变量
{
"key1":"value1"
}
{
"key2":"value2"
}
  {
    ”key1”:”value1”
  },
  {
    ”key2”:”value2”
  }
{
"key1":"value11"
}
  {
    ”key1”:”value1”
  },
  {
    ”key2”:”value2”
  }
  {
    ”key1”:”value11”
  },
  {
    ”key2”:”value2”
  }