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

流程和步骤

通常,流程和步骤之间,流程的多个步骤之间需要传递数据。和函数式编程语言类似,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 开始时,其 local10 为空 。因为 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”
  }