コネヒト開発者ブログ

コネヒト開発者ブログ

AWS Step Functionsに新たに14個追加された関数をいくつか使ってみた

こんにちは、インフラエンジニアのささしゅう(@sasashuuu)です。 本日は最近アップデートされ、新たに14個追加されたStep Functionsの組み込み関数について、いくつか活用事例を交えてご紹介したいと思います。

アップデートの概要

Step Functionsの組み込み関数アップデートに関するアナウンスは以下になります。 aws.amazon.com

また、組み込み関数のドキュメントは以下になりますが、日本語版のドキュメントにはまだアップデートされた関数の情報の記載が見当たらなかったため、英語版の方を貼っておきます。 docs.aws.amazon.com

配列操作、UUIDの作成、JSONオブジェクトの結合など計14個の組み込み関数のアップデートがありました。これらの組み込み関数はASL(Amazon States Language)というStep Functionsのリソースを定義するのに使用するJSONベースの構造化言語に直接追加されているため、そのまますぐにでもStep Functionsから利用することができます。

アップデートされた組み込み関数の種類や挙動に関してはクラスメソッド様が以下の記事で網羅的に取り上げられているので、こちらが参考になります。 dev.classmethod.jp

活用事例

ここからは、具体的な活用事例をご紹介します。 今回ご紹介するのは以下の5つです。

States.UUID

まずは1つめの States.UUID です。これは、v4 UUIDの乱数を返してくれる関数です。

活用事例としては、ワークフロー上で作成するAuroraクラスターとそれに紐づくインスタンスについて、既存リソースの識別子の命名をベースにしたいが、サフィックスとしてランダムな識別子を入れて被らないように命名したいというケースで活用しました。

次のようなイメージです。

  • 既に存在するリソースの命名(Auroraクラスターの識別子)
    • dev-hoge-aurora-cluster
  • あたらしく作りたいリソースの命名(Auroraクラスターの識別子)
    • dev-hoge-aurora-cluster-{ランダムな識別子}

ワークフロー実行時の入力です。

{
    "HogeDbClusterIdentifier": {
        "Identifier": "dev-hoge-aurora-cluster-{}"
    }
}

Pass ステートで States.UUID を使用して、生成した識別子を元の入力を上書きする形で更新します。

    "Pass": {
        "Type": "Pass",
        "Next": "Foo",
        "Parameters": {
            "Identifier.$": "States.Format($.HogeDbClusterIdentifier.Identifier,States.UUID())"
        },
        "ResultPath": "$.HogeDbClusterIdentifier"
    }

Auroraクラスターを作成します。(下記は RestoreDBClusterFromSnapshot を用いたスナップショットからの復元の例です。)

    "RestoreDBClusterFromSnapshot": {
        "Type": "Task",
        "Next": "Pass",
        "Parameters": {
            "DbClusterIdentifier.$": "$.HogeDbClusterIdentifier.Identifier",
            "Engine": "aurora",
            "SnapshotIdentifier": "hoge"
        },
        "Resource": "arn:aws:states:::aws-sdk:rds:restoreDBClusterFromSnapshot"
    }

結果、次のようなUUIDがサフィックスとしてついた識別子のクラスタが作成されます。(※インスタンス作成のステップはここでは割愛。)

dev-hoge-aurora-cluster-a7cae12b-3e92-4c7a-b2f5-07416d023dbc

States.StringSplit

続いては States.StringSplit です。これは、文字列を区切り文字で値の配列に変換してくれる関数です。 何かのパラメータを複数設定したい際などに便利で、活用事例としては、RDSのクラスター作成時のSGなどの指定で利用しました。

ワークフロー実行時の入力です。

{
    "SecurityGroups": "sg-01xxxxxx,sg-02xxxxxx"
}

続いてスナップショットからクラスターを復元する RestoreDBClusterFromSnapshot ステートの定義です。

    "RestoreDBClusterFromSnapshot": {
        "Type": "Task",
        "Parameters": {
          ...
          "VpcSecurityGroupIds.$": "States.StringSplit($.SecurityGroups, ',')",
                  ...
        },
        "Resource": "arn:aws:states:::aws-sdk:rds:restoreDBClusterFromSnapshot",
        "Next": "CreateDBInstance",
        "Catch": [
            {
                "ErrorEquals": [
                    "States.ALL"
                ],
                "Next": "NotifySlackFailureTheOthers"
            }
        ],
        "ResultPath": "$.Hoge"
    },

入力で渡していた SecurityGroupsStates.StringSplit の関数の第1引数に、区切り文字としてカンマを第2引数に与えることにより、API実行時のパラメータの指定を次のように展開してくれます。

    {
        ...
        "parameters": {
          ...
          "VpcSecurityGroupIds": [
                "sg-01xxxxxx",
                "sg-02xxxxxx"
            ],
          ...
        },
        ...
    }

States.MathAdd、States.ArrayLength、States.ArrayGetItem

最後にこの3つの関数の活用事例をまとめて紹介します。 ざっくり関数の性質を説明すると次のような内容です。

  • States.MathAdd
    • 引数に与えた2つの数値の合計値を返す。
  • States.ArrayLength
    • 引数に与えた配列の長さを返す。
  • States.ArrayGetItem
    • 引数に与えた配列とインデックスから対象の値を返す。

この3つの関数は一連のフローで組み合わせて次のようなケースで活用しました。

  1. ワークフローで実行するAPIのresponseのoutputが配列で返る
  2. States.ArrayLength で配列の長さを取得する
  3. 配列データをループ処理にかける
  4. States.ArrayGetItem で対象の配列とCounterとして用意していたパラメータを使用し、インデックスを指定して要素を取り出す
  5. 取り出した要素が処理の対象の要素かどうかを Choice ステートで判定する
  6. 対象の要素一致していなければCounterとして用意していたパラメータを States.MathAdd でインクリメントする、インクリメントが配列の長さに達している場合は処理をループ処理を終了する
  7. 再びループの先頭に戻る
  8. 繰り返し…

実際に簡略化して再現したものが次のようなものになります。

{
    "Comment": "A description of my state machine",
    "StartAt": "DescribeDBClusters",
    "States": {
        "DescribeDBClusters": {
            "Type": "Task",
            "Next": "Pass",
            "Parameters": {},
            "Resource": "arn:aws:states:::aws-sdk:rds:describeDBClusters",
            "ResultPath": "$.Output",
            "ResultSelector": {
                "Length.$": "States.ArrayLength($.DbClusters)",
                "DbClusters.$": "$.DbClusters"
            }
        },
        "Pass": {
            "Type": "Pass",
            "Next": "Choice",
            "Parameters": {
                "DbClustersLength.$": "States.MathAdd($.Output.Length, -1)",
                "DbCluster.$": "States.ArrayGetItem($.Output.DbClusters, $.Counter.Val)"
            },
            "ResultPath": "$.Tmp"
        },
        "Choice": {
            "Type": "Choice",
            "Choices": [
                {
                    "Variable": "$.Tmp.DbCluster.DbClusterIdentifier",
                    "StringMatches": "dev-hoge-aurora-*",
                    "Next": "Pass (2)"
                }
            ],
            "Default": "Pass (1)"
        },
        "Pass (1)": {
            "Type": "Pass",
            "Next": "Choice (1)",
            "ResultPath": "$.Counter",
            "Parameters": {
                "Val.$": "States.MathAdd($.Counter.Val, 1)"
            }
        },
        "Choice (1)": {
            "Type": "Choice",
            "Choices": [
                {
                    "Variable": "$.Tmp.DbClustersLength",
                    "NumericEqualsPath": "$.Counter.Val",
                    "Next": "Fail"
                }
            ],
            "Default": "Pass"
        },
        "Fail": {
            "Type": "Fail"
        },
        "Pass (2)": {
            "Type": "Pass",
            "End": true
        }
    }
}

DescribeDBClusters で全てのクラスターを取得し、先述したようなフローの処理を行なっています。

ワークフロー実行時の入力です。

{
    "Counter": {
      "Val": 0
    }
}

1つずつステートの処理を見ていきます。

最初の DescribeDBClusters ステートでは States.ArrayLength を使用し、 Output.Length のパスで取得したデータの長さを取得、Output.DbClusters のパスで取得したデータを丸ごとを取得し、元の入力と合わせて次のステートに引き継いでいます。

    "DescribeDBClusters": {
        "Type": "Task",
        "Next": "Pass",
        "Parameters": {},
        "Resource": "arn:aws:states:::aws-sdk:rds:describeDBClusters",
        "ResultPath": "$.Output",
        "ResultSelector": {
            "Length.$": "States.ArrayLength($.DbClusters)",
            "DbClusters.$": "$.DbClusters"
        }
    }

次の Pass ステートでは、States.MathAdd を使用し、Tmp.DbClustersLength のパスで本来の配列データから1を引いた数を取得、States.ArrayGetItem を使用し、Tmp.DbCluster のパスで対象のクラスター情報を取得し、次のステートに引き継いでいます。

    "Pass": {
        "Type": "Pass",
        "Next": "Choice",
        "Parameters": {
            "DbClustersLength.$": "States.MathAdd($.Output.Length, -1)",
            "DbCluster.$": "States.ArrayGetItem($.Output.DbClusters, $.Counter.Val)"
        },
        "ResultPath": "$.Tmp"
    }

さらに Choice ステートでは ループ上で絞り込んで取得している対象が dev-hoge-aurora-* というクラスターの識別子と一致しているかをチェックしています。

    "Choice": {
        "Type": "Choice",
        "Choices": [
            {
                "Variable": "$.Tmp.DbCluster.DbClusterIdentifier",
                "StringMatches": "dev-hoge-aurora-*",
                "Next": "Pass (2)"
            }
        ],
        "Default": "Pass (1)"
    }

一致していなかった際は Pass (1) へ移り、用意していた入力のCounterをインクリメントします。States.MathAdd を使い数値の1を追加しています。

    "Pass (1)": {
        "Type": "Pass",
        "Next": "Choice (1)",
        "ResultPath": "$.Counter",
        "Parameters": {
            "Val.$": "States.MathAdd($.Counter.Val, 1)"
        }
    }

もし、ここでインクリメントしていたCounterの数値が、取得していた配列データの数に達した場合は Choice (1) の判定により、処理が終了するようになっています。

    "Choice (1)": {
        "Type": "Choice",
        "Choices": [
            {
                "Variable": "$.Tmp.DbClustersLength",
                "NumericEqualsPath": "$.Counter.Val",
                "Next": "Fail"
            }
        ],
        "Default": "Pass"
    }

以上のように配列の操作系の組み込み関数も強化されているので組み合わせて柔軟に利用することができます。

余談ですが、ループ系の処理に関しては Map ステートという入力配列の要素ごとに一連のステップを実行してくれるものがあるのですが、ループ途中のBreakやそこからループ内の任意のoutputをもとの入力に統合するような実装などが難しいように感じ、今回は上記の組み込み関数を使用し、配列のループ操作を行いました。

おわりに

Step Functionsの組み込み関数が大幅にアップデートされ、ちょっとした計算処理や整形処理などがLambdaなどのリソースを使わなくてもワークフロー上で行えるようになり、非常に便利になりました。みなさんも機会があればぜひ積極的に活用することをおすすめします。