1. Home
  2. テクノロジー
  3. AWS Step Functionsで新たにサポートされたJSONataを使ってみた!

AWS Step Functionsで新たにサポートされたJSONataを使ってみた!

この記事は、ニフティグループ Advent Calendar 2024 23日目の記事です。

はじめに

こんにちは!ニフティライフスタイルで不動産サービスの開発をしているsaikeiです!

少し前までモバイルアプリの開発をメインで行っていたのですが、最近はAWSを用いたバックエンド側の開発にも携わっています。

今回は、先月AWSから発表された「Step FunctionsでのJSONataサポート」について実際に触る機会があったので、ご紹介したいと思います。

JSONataとは何か

JSONataとはJSONデータ用の軽量クエリおよび変換言語で、最小限の構文で洗練されたクエリ式が特徴です。(公式サイト

データの操作や結合をするための演算子や、ユーザー定義関数を作成することもでき、結果を任意のJSON構造フォーマットで出力することができます。

例えば以下のようなJSONに対して、JSONataで $sum(Fruits.(price * quantity)) というクエリを実行すると「果物ごとに price × quantity を計算し合計した値」 4200 が結果として出力されます。

// JSON
{
  "Fruits" : [
    {
      "name": "apple",
      "price": 100,
      "quantity": 2
    },
    {
      "name": "banana",
      "price": 200,
      "quantity": 5
    },
    {
      "name": "orange",
      "price": 300,
      "quantity": 10
    }
  ]
}

// JSONata
$sum(Fruits.(price * quantity))
-> 4200

JSONata公式で実際に試せる「JSONata Exerciser」というWebツールが用意されているので合わせてご確認ください。

実際にStep Functionsで使ってみる

今回実装する仕様

以下の処理を行うワークフローを作成していきます。

  1. 重複起動チェック
    • GetListExecutions 実行中のStep Functionsの数を取得
    • CheckMultipleLaunches 自身を含めて2台以上起動している場合は処理を終了
  2. 未処理アイテムチェック
    • GetParameterStore Parameter Storeから値を取得
    • CheckUpdateUnprocessedItem 未処理アイテムが0の場合は処理を終了
  3. 処理を行う別のState Machineを起動
    • RunOtherStateMachine Step Functionsを実行

※ 全てにおいてエラーが発生した場合は Fail として終了させる

今までのJSONPathでの記述の場合、後続のステップに値を渡すためには、各ステップでOutputを記述するバケツリレーが必要でした。

しかし今回JSONataに対応したことで「変数」が使えるようになったため、以下のように変数に値を入れることで後続のすべてのステップから値を呼び出すことができます。

{
  "selectedFruits": "apple",
  "quantity": 30 
}

詳しくは公式の「Processing input and output in Step Functions」をご覧ください。

ワークフロー定義

0. ベース定義

QueryLanguage のデフォルトは JSONPath のため、JSONata を使うためには明示的に宣言する必要があります。

また「失敗/成功用」のステートもこの段階で用意しておきます。

QueryLanguage: JSONata
States:
  Succeed:
    Type: Succeed
  Fail:
    Type: Fail

1. 重複起動チェック

arn:aws:states:::aws-sdk:sfn:listExecutions に引数で自身のStateMachineArnを渡すことで、結果を $states.result からJSON形式で取得できます。

今回は実行数を取得したいため、$states.result.Executions$count() で囲むことで「現在の実行数」を取得することができます。

これをJSONataで使えるようになった変数にassignすることで、後続のステップで値を呼び出すことができます。

GetListExecutions:
  Type: Task
  Arguments:
    StateMachineArn: {自身のStateMachineArnを記載}
    StatusFilter: RUNNING
  Resource: arn:aws:states:::aws-sdk:sfn:listExecutions
  Next: CheckMultipleLaunches
  Assign:
    runningStateMachineCount: >-
      '{% $count($states.result.Executions) %}'
ワークフローエディタでの表示

先ほどのステップにより $runningStateMachineCount で実行数が取得できるようになったため、 Choice を使って分岐を作成し「現在の実行数が2未満の場合」に後続の処理が実行されるようにします。

CheckMultipleLaunches:
  Type: Choice
  Choices:
    - Next: GetParameterStore
      Condition: '{% $runningStateMachineCount < 2 %}'
  Default: Succeed

2. 未処理アイテムチェック

Resourcearn:aws:states:::aws-sdk:ssm:getParameter を指定し、引数として Name にパラメーター名を渡すことでParameter Storeから値を取得することができます。

結果は $states.result に格納されているため、先ほどと同様に新たな変数にassignすることで後続のステップで値を呼び出すことができます。

またParameter Storeからの取得に失敗した場合はエラーが投げられるので、 Catch を定義することでエラー時の処理の記述ができます。

GetParameterStore:
  Type: Task
  Resource: arn:aws:states:::aws-sdk:ssm:getParameter
  Arguments:
    Name: /parameter/unprocessed_items_count
  Assign:
    unprocessedItemsCount: >-
      '{% $states.result.Parameter.Value %}'
  Next: CheckUpdateUnprocessedItem
  Catch:
    - ErrorEquals:
        - States.ALL
      Next: Fail

CheckUpdateUnprocessedItem1. 重複起動チェックChoice と同様の処理のため省略します

3. 処理を行う別のState Machineを起動

Resourcearn:aws:states:::states:startExecution.sync:2 を指定し、引数として StateMachineArn にStateMachineのarnを渡すことで該当のState Machineを起動することができます。

呼び出し先で処理が失敗した場合のエラーハンドリングは Catch を用いることで指定することができます。(以下の例ではそのままFailとしています)

RunOtherStateMachine:
Type: Task
Resource: arn:aws:states:::states:startExecution.sync:2
Next: Succeed
Catch:
  - ErrorEquals:
      - States.ALL
    Next: Fail
Arguments:
  StateMachineArn: {別のStateMachineArnを記載}

ワークフローの結合

これまでに紹介したステップを繋げることで、冒頭に記載の仕様を満たすワークフローを作成することができます。コードの全容は長くなってしまうため記事の末尾に記載します。

さいごに

今回は Step Functions 定義を JSONata を用いて記述してみました。

私は初めての Step Functions 作成が JSONata だったので、JSONPath との比較はできませんが、コードを見る限りではとてもシンプルかつ分かりやすい形になったかなと思います。

また、今まではLambdaを経由しなければならなかった処理についても、JSONataを使うことで、ワークフローの一部として定義することができるようになったため、こちらも活用していきたいと思っています。

この記事が何か一つでもみなさんの参考になれば幸いです。
最後までお読みいただきありがとうございました!


おまけ:JSONataで定義したStep Functionsのワークフロー(全文)

QueryLanguage: JSONata
StartAt: GetListExecutions
States:
  GetListExecutions:
    Type: Task
    Arguments:
      StateMachineArn: {自身のStateMachineArnを記載}
      StatusFilter: RUNNING
    Resource: arn:aws:states:::aws-sdk:sfn:listExecutions
    Next: CheckMultipleLaunches
    Assign:
      runningStateMachineCount: >-
        '{% $count($states.result.Executions) %}'

  CheckMultipleLaunches:
    Type: Choice
    Choices:
      - Next: GetParameterStore
        Condition: '{% $runningStateMachineCount < 2 %}'
    Default: Succeed

  GetParameterStore:
    Type: Task
    Resource: arn:aws:states:::aws-sdk:ssm:getParameter
    Arguments:
      Name: /parameter/unprocessed_items_count
    Assign:
      unprocessedItemsCount: >-
        '{% $states.result.Parameter.Value %}'
    Next: CheckUpdateUnprocessedItem
    Catch:
      - ErrorEquals:
          - States.ALL
        Next: Fail

  CheckUpdateUnprocessedItem:
    Type: Choice
    Choices:
      - Next: RunOtherStateMachine
        Condition: '{% $unprocessedItemsCount > 0 %}'
    Default: Succeed

  RunOtherStateMachine:
    Type: Task
    Resource: arn:aws:states:::states:startExecution.sync:2
    Next: Succeed
    Catch:
      - ErrorEquals:
          - States.ALL
        Next: Fail
    Arguments:
      StateMachineArn: {別のStateMachineArnを記載}
  
  Succeed:
    Type: Succeed
  
  Fail:
    Type: Fail

この記事をシェア

掲載内容は、記事執筆時点の情報をもとにしています。