2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クラベスAdvent Calendar 2024

Day 14

Stepfunctionsの「Variables」機能を触ってみた

Posted at

はじめに

初めまして、株式会社クラベスでエンジニアをしているDandeと申します!

今回、初めてテックブログを書くことになり、何を書けば良いか悩んでいましたが、最近実案件でStep Functionsの新機能を試したので、その感想をまとめることにしました。

拙い文章かもしれませんが、どなたかの参考になれば幸いです。

また、説明に誤りなどあればご指摘いただければ幸いです。


新機能を使用する前の経緯

あるプロジェクトで、Step FunctionsをTerraformで構築する依頼を受けました。

具体的な内容はお伝えできませんが、ステートマシン内で複数のECSタスクを同期的に実行する仕様で、それぞれが別々のバッチ処理を実行するというものです。

私自身、Step Functionsを触るのも、Terraformを本格的に使うのも初めての状況でした。

ドキュメントと格闘しながら実装を進めていましたが、中でも一番大変だったのがステート間の値の受け渡しでした。

例えば、以下のように4つのステートがあるとします。

ステート1 ← 入力が渡される
  ↓
ステート2
  ↓
ステート3
  ↓
ステート4 ← ステート1に入力として渡された値を使用

この場合、ステート1で渡された入力値をステート4でも使用するには、ResultPathを利用してデータを次のステートに渡していく必要があります。

しかし、入力値を使用しないステート(ステート2やステート3)にも値を渡さなければならないため、この処理が煩雑になります。


バケツリレーの課題

簡単な例で見てみましょう。

definition = jsonencode({
    StartAt = "State1",
    State1 = {
        Type       = "Task"
        ・
        ・
        ResultPath = null
        Next       = "State2"
    },
    State2 = {
        Type       = "Task"
        ・
        ・
        ResultPath = null
        Next       = "State3"
    },
    State3 = {
        Type       = "Task"
        ・
        ・
        ResultPath = null
        Next       = "State4"
    },
    State4 = {
        Type       = "Task"
        ・
        ・
        Parameters = {
            LaunchType       = "FARGATE"
            Cluster          = "XXXXXXXX"
            ・
            ・
            Overrides = {
                ContainerOverrides = [
                    {
                        "Name" : "app",
                        "Environment" : [
                            {
                                "Name" : "EVENT_TIMESTAMP",
                                "Value.$" : "$.event_time"
                            }
                        ]
                    }
                ]
            }
        }
        End = true
    }
})

上記では、ResultPath = nullを設定することで、入力データをそのまま次のステートに渡しています。

これを全てのステートで繰り返すことで、最終的にState4までデータを渡せます。

しかし、次のようなケースになると複雑さが増します。例えば、ステート2のタスクが失敗した場合にエラー通知用のステートを経由して、ステート3に戻るようにしたいとします。

State2 = {
    Type       = "Task"
    ・
    ・
    ResultPath = null
    Next       = "State3"
    Catch      = [{
        ErrorEquals = ["States.ALL"],
        Next        = "ErrorNotifyState"
        ResultPath  = null
    }]
},
ErrorNotifyState = {
    Type       = "Task"
    ・
    ・
    ResultPath = null
    Next       = "State3"
}

このように、エラーハンドリングを加えると、さらにResultPath = nullの記述が増えます。

記述ミスが発生しやすく、例えばCatch句で値の受け渡しを忘れた場合、State4に必要なデータが渡らずエラーになることもあります。

また、入力データだけでなくタスク結果の結合が必要な場合は、さらにコードが煩雑になります。

実装中は、

  • 「どのステートにどのデータを渡しているのか?」
  • 「受け取るデータの構造はどうなっているのか?」

といった疑問が頻繁に生じ、開発効率が下がっていました。


待望の新機能「Variables」の登場

2024年11月、私の願いが通じたのか、AWSから待望の新機能が発表されました!

AWS Step FunctionsがVariablesとJSONata変換で開発者体験をシンプルにする

この新機能には、「Variables」と「JSONata変換」の2つがあります。

今回は主に「Variables」について紹介します(JSONata変換についてはまた別の機会に記事にしたいと思います)。


Variablesでの実装例

Variablesを使うことで、バケツリレーを廃止できます。

以下に修正版のコードを示します。

definition = jsonencode({
    StartAt = "State1",
    State1 = {
        QueryLanguage = "JSONata"
        Type          = "Task"
        ・
        ・
        Assign = {
            "eventTime" : "{% $states.input.event_time %}"
        }
        Next          = "State2"
    },
    State2 = {
        Type = "Task"
        ・
        ・
        Next = "State3"
        Catch = [{
            ErrorEquals = ["States.ALL"],
            Next        = "ErrorNotifyState"
        }]
    },
    State3 = {
        Type = "Task"
        ・
        ・
        Next = "State4"
    },
    State4 = {
        Type       = "Task"
        ・
        ・
        Parameters = {
            Overrides = {
                ContainerOverrides = [
                    {
                        "Name" : "app",
                        "Environment" : [
                            {
                                "Name" : "EVENT_TIMESTAMP",
                                "Value.$" : "$eventTime"
                            }
                        ]
                    }
                ]
            }
        }
        End = true
    }
})

ここで注目すべきは、以下のポイントです

  • Assignによる変数定義
    State1でeventTimeという変数を定義しています。この変数には、入力データからevent_timeを取得して格納しています。

    Assign = {
        "eventTime" : "{% $states.input.event_time %}"
    }
    
  • 後続ステートでの利用
    変数は、必要なステートで$変数名として簡単に使用できます。例えば、State4で$eventTimeをコンテナの環境変数として設定しています。

これにより、バケツリレーが不要となり、コードが大幅に簡潔になります。

必要な値だけを変数に格納すれば、後続のステートで自由に利用できるため、実装ミスも減らせます。


終わりに

Variables機能の追加は、Step Functionsの利便性を飛躍的に向上させました。

もし現行のシステムでバケツリレーによる値の受け渡しに苦労しているなら、ぜひこの機能を試してみてください。

この記事が、Step Functionsを利用する多くの方々の助けになれば嬉しいです。


参考資料

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?