AWS Step Functions の Map 処理で出力される配列を組み込み関数で検証して、ステートマシン全体の成功・失敗の分岐をする

AWS Step Functions 上で配列を操作できる組み込み関数の一つである States.ArrayContains を使ったステートマシンを作成してみました。
2023.01.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは、筧( @TakaakiKakei )です。

AWS Step Functions の Map 処理の出力には配列が返されます。 今まで、こちらの配列の内容に応じて条件分岐を行う場合は、ステートマシンで "Type": "Task"を使い、Lambda を呼び出すことが多かったと思います。 ところで、先日ステートマシン上で配列を操作できる組み込み関数が追加されました。

こちらの組み込み関数を利用すれば、Lambda を利用せずステートマシン上で条件分岐を完結できそうだなと思ったので調査してみました。 今回は調査結果を汎化してご紹介します。

結論

結論を最初に記述します。

  • States.ArrayContains組み込み関数で、配列に特定の値が存在するか判断可能。
  • Map 処理の出力は一次元配列にして、States.ArrayContains組み込み関数から参照しやすいようにするのがベター。
  • States.ArrayContains組み込み関数は、Type: Passで使い、その結果をType: Choiceで利用するのが良さそう。

やってみた

以下のようなステートマシンを作成しました。

以下がステートマシンのコードです。

state-machines.yml

name: Test-Blog-dev
definition:
  StartAt: Map
  States:
    Map:
      Type: Map
      ItemsPath: $
      MaxConcurrency: 3
      Iterator:
        StartAt: Work
        States:
          Work:
            Type: Task
            Parameters:
              input.$: $
              execution.$: $$
            Resource:
              Fn::GetAtt: [work, Arn]
            Catch:
              - ErrorEquals:
                  - States.ALL
                ResultPath: $.error_info
                Next: Error
            Next: Done
          Done:
            Type: Task
            Parameters:
              input.$: $
              status: 'Success'
              execution.$: $$
            ResultPath: $ # Lambda で 'Success' という文字列が return されるようにしてください
            Resource:
              Fn::GetAtt: [slack, Arn]
            End: true
          Error:
            Type: Task
            Parameters:
              input.$: $
              status: 'Fail'
              execution.$: $$
            ResultPath: $ # Lambda で 'Fail' という文字列が return されるようにしてください
            Resource:
              Fn::GetAtt: [slack, Arn]
            End: true
      ResultPath: $
      Next: StatusPass
    StatusPass:
      Type: Pass
      Parameters:
        # Map の出力結果に Fail が含まれる場合、is_fail に true が返される。含まれない場合、is_fail に false が返される。
        # refs: https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html#asl-intrsc-func-arrays
        is_fail.$: States.ArrayContains($, 'Fail')
      Next: StatusChoice
    StatusChoice:
      Type: Choice
      Default: FailState
      Choices:
        - Variable: $.is_fail
          BooleanEquals: false # Map の出力結果がすべて Success の場合
          Next: SuccessState
    SuccessState:
      Type: Succeed
    FailState:
      Type: Fail

解説

コードを3分割して解説します。 3つ目が本ブログのメインの内容です。

1~24行目: Map 処理の冒頭

state-machines.yml

name: Test-Blog-dev
definition:
  StartAt: Map
  States:
    Map:
      Type: Map
      ItemsPath: $
      MaxConcurrency: 3
      Iterator:
        StartAt: Work
        States:
          Work:
            Type: Task
            Parameters:
              input.$: $
              execution.$: $$
            Resource:
              Fn::GetAtt: [work, Arn]
            Catch:
              - ErrorEquals:
                  - States.ALL
                ResultPath: $.error_info
                Next: Error
            Next: Done
  • Map 処理を始めから定義しています。最初は Work という Task ステートを宣言しており、work という lambda 関数を呼び出しています。work 関数の内容は任意ですので割愛します。
  • work 関数が正常終了すれば、Done という Task ステートに進みます。
  • work 関数で例外が発生すれば、Error という Task ステートに進みます。

25~46行目: Map 処理内の成功・失敗の分岐

state-machines.yml

          Done:
            Type: Task
            Parameters:
              input.$: $
              status: 'Success'
              execution.$: $$
            ResultPath: $ # Lambda で 'Success' という文字列が return されるようにしてください
            Resource:
              Fn::GetAtt: [slack, Arn]
            End: true
          Error:
            Type: Task
            Parameters:
              input.$: $
              status: 'Fail'
              execution.$: $$
            ResultPath: $ # Lambda で 'Fail' という文字列が return されるようにしてください
            Resource:
              Fn::GetAtt: [slack, Arn]
            End: true
      ResultPath: $
      Next: StatusPass
  • Done という Task ステートでは、slack という lambda 関数を呼び出しています。内容は省略しますが、ここでは関数内で正常終了したことを Slack 通知する想定です。関数内容は任意ですが、後述の処理のために return で、'Success' という文字列のみを返すようにしてください。End: trueの記述があるので、Map 処理はこちらのステートで終わっています。
  • Error という Task ステートでは、slack という lambda 関数を呼び出しています。同じく内容は省略しますが、ここでは関数内で異常終了したことを Slack 通知する想定です。関数内容は任意ですが、後述の処理のために return で、'Fail' という文字列のみを返すようにしてください。End: trueの記述があるので、Map 処理はこちらのステートで終わっています。
  • ResultPath: $としているので、Map の処理結果は下記のような配列で出力され、StatusPass という Pass ステートに渡されます。
['Success','Fail','Success']

47~64行目: ステートマシン全体の成功・失敗の分岐

state-machines.yml

    StatusPass:
      Type: Pass
      Parameters:
        # Map の出力結果に Fail が含まれる場合、is_fail に true が返される。含まれない場合、is_fail に false が返される。
        # refs: https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html#asl-intrsc-func-arrays
        is_fail.$: States.ArrayContains($, 'Fail')
      Next: StatusChoice
    StatusChoice:
      Type: Choice
      Default: FailState
      Choices:
        - Variable: $.is_fail
          BooleanEquals: false # Map の出力結果がすべて Success の場合
          Next: SuccessState
    SuccessState:
      Type: Succeed
    FailState:
      Type: Fail
  • StatusPass という Pass ステート では、States.ArrayContains()を利用して、Map 処理の出力の配列内に、特定の文字列が存在するかを判断します。今回の場合、'Fail'という文字列が存在する場合は ture が、'Fail'という文字列が一つも存在しない場合は false が、is_fail に渡されます。
  • StatusChoice という Choice ステートでは、is_fail の真偽値を BooleanEquals で評価します。false の場合は Map の出力結果がすべて 'Success' と判断して、SuccessState に分岐して成功で終わります。ture の場合は Map の出力結果に 'Fail' が含まれると判断して、FailState に分岐して失敗で終わります。

補足

Map 処理で一部処理が失敗しても中断しない方法

Map 処理で一部処理が失敗したら他処理が中断するのでは?と思われる方もいるかもしれません。 この場合、Catch フィールドで次のステートに渡すことで中断しないようにできます。 例えば、今回のコードでは、Work という Task ステートで処理が失敗しても上記によって中断されません。 一方で、Done と Error の Task ステートで処理が失敗すると、ステートマシン全体が中断されますが、エラーが起こりづらい簡単な処理にする想定なので、ここではリスク受容しました。

States.ArrayContains組み込み関数に渡す配列

Map 処理の出力は配列です。 そして、当該関数の第一引数にはインプットとなる配列を、第二引数には検索する値をて有効な JSON オブジェクトで指定します。 もし Map 処理の出力が多次元配列だと、第一引数に指定したい配列が深い階層に出力されることになるので、当該組み込み関数を利用した検証が難しくなると感じました。 なので、以下のような一次元配列が出力されるように調整するのがベターかなと思いました。

['Success','Fail','Success']

難しかったところ

States.ArrayContains組み込み関数を使うステート

最初は、Choice ステートの StatusCheck で当該組み込み関数を利用しようとしました。 ところが、以下のような SCHEMA_VALIDATION_FAILED が発生してしまい、うまく利用できませんでした。

state-machines.yml(失敗例)

    StatusChoice:
      Type: Choice
      Default: FailState
      Choices:
        - Variable: States.ArrayContains($, 'Fail')
          BooleanEquals: false
          Next: SuccessState

Resource handler returned message: "Invalid State Machine Definition: 'SCHEMA_VALIDATION_FAILED: Value is not a Reference Path: Reference path didn't start with '$' at ..snip..

そこで、Pass ステートの StatusPass を前段に追加して、当該組み込み関数を利用しました。

state-machines.yml

    StatusPass:
      Type: Pass
      Parameters:
        # Map の出力結果に Fail が含まれる場合、is_fail に true が返される。含まれない場合、is_fail に false が返される。
        # refs: https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html#asl-intrsc-func-arrays
        is_fail.$: States.ArrayContains($, 'Fail')
      Next: StatusChoice
    StatusChoice:
      Type: Choice
      Default: FailState
      Choices:
        - Variable: $.is_fail
          BooleanEquals: false # Map の出力結果がすべて Success の場合
          Next: SuccessState

今後、States.ArrayContains組み込み関数を、Choice ステートで使う方法が確認できたら別途ご紹介したいと思います。

おわりに

最後まで読んでいただきありがとうございます。

組み込み関数で配列を操作できるようになったことで、Map 処理の出力結果を扱いやすくなりました。 他の組み込み関数も今後利用していきたいと思います。

それではまた!