AWS SAMがStep Functionsに対応しました!

先日AWS SAMでStep Functionsをサポートするようになりました!今回はSAMでStep Functionsを使い、記述方法や使用する言語などを紹介してみます。
2020.05.31

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

はじめに

CX事業本部の佐藤智樹です。先日AWS SAMがStep Functionsをサポートするようになりました!

以前からSAMでStep Functionsを使用できなくは無かったのですが正式サポートされたことでより記述しやすくなりました。今回はSAMでStep Functionsを使い、記述方法や使用する言語などを紹介してみたいと思います。紹介する中で一部のソースは以下の参考URLのものを使用しています。

以前からSAMを使っている方やこれからSAMを使う方でStep Functionsを組み込みたい場合に、大まかに理解するための最初の一歩として読んでいただければ幸いです。

SAMでStep Functionsを利用する場合のパターン

SAMからStep Functionsを扱う場合大まかに2つのパターンが提供されています。1つ目はSAMのリソース記述で使用するYAMLファイルに書くパターンです。2つ目はASL(Amazon States Language.)で記述したStep Functionsのステートマシン構成を呼び出すパターンです。これら2つのパターンについて次節以降で解説していきます。

YAMLテンプレートに記述する場合

YAMLで記述する場合は以下のように記述します。例は処理を受け取って流すだけの状態遷移です。

template.yaml

# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  SimpleStateMachine:
    Type: AWS::Serverless::StateMachine
    Properties:
      Definition:
        StartAt: Single State
        States:
          Single State:
            Type: Pass
            End: true
      Policies:
        - CloudWatchPutMetricPolicy: {}

上記のテンプレートをデプロイするとAWSのWebコンソールから以下のステートマシン図と生成されたJSONが確認できます。

Typeの部分にステートの種類を記述します。Type以降の記述はそれぞれTypeによって異なります。現在SAMでは以下全てのStep FunctionsのTypeをサポートしています。

項目名 説明
Task 単一の作業
Choice 分岐
Parallel 並列処理
Wait 指定時間分の遅延
Fail 実行の失敗
Succeed 実行の成功
Pass 入力を出力に渡す
Map 動的並列処理

SAMの プルリクを確認したところType:WaitとType:Mapのテストコードは無かったです。しかしTypeをWaitとMapに設定して試したところテンプレート出力から実行まで正常にできました。Mapについては後ほどの記述でも紹介します。

実例(Type:Passの場合)

本項ではTypeをPassとしてType以下のオプションを指定した場合の記述を紹介します。Type以下にYAMLの記述を追加すると定義に変換されます。例えばTypeがPassでResultとResultPathのオプションを加えるときは以下のような記述になります。

template.yaml(Resources配下のみ)

Resources:
  SimpleStateMachine:
    Type: AWS::Serverless::StateMachine
    Properties:
      Definition:
        StartAt: Single State
        States:
          Single State:
            Type: Pass
            Result:
              Value: 1
            ResultPath: "$.coords"
            End: true
      Policies:
        - CloudWatchPutMetricPolicy: {}

Webコンソールを見ると以下のようなJSONが生成されます。

{
    "StartAt": "Single State",
    "States": {
        "Single State": {
            "End": true,
            "Result": {
                "Value": 1
            },
            "ResultPath": "$.coords",
            "Type": "Pass"
        }
    }
}

Webコンソールから以下のようにデフォルトの入力で実行すると、正常に動作することが確認できます。

ちなみにですがテンプレートに誤記があった場合はデプロイ時にエラーになります。例えば上記のtemplate.yamlで ResultPath のオプションを ResultPat に書き換えた場合は以下のようなエラーが出ます。

UPDATE_FAILED            AWS::StepFunctions::St   SimpleStateMachine       Invalid State Machine  
                         ateMachine                                        Definition: 'SCHEMA_VA 
                                                                           LIDATION_FAILED: Field 
                                                                           'ResultPah' is not     
                                                                           supported at           
                                                                           /States/Single State'  
                                                                           (Service:              
                                                                           AWSStepFunctions;      
                                                                           Status Code: 400;      
                                                                           Error Code:            
                                                                           InvalidDefinition;     
                                                                           Request ID: xxxxxxxx-x 
                                                                           xxx-xxxx-xxxx-xxxxxxxx 
                                                                           xxxx; Proxy: null)     
UPDATE_ROLLBACK_IN_PRO   AWS::CloudFormation::S   simple-state-machine     The following          
GRESS                    tack                                              resource(s) failed to  
                                                                           update:                
                                                                           [SimpleStateMachine].

実例(Type:Mapの場合)

今度はMapの場合を紹介します。Mapの場合もType以下でMapが必要とするオプションを指定するとデプロイ時にテンプレートが生成されます。テンプレートが出力・動作できるか簡単なもので試します。処理としてはイベントのJSONからCommentの配列を受け取ってMap処理の中で3秒Waitする状態遷移を定義します。

template.yaml(Resource配下のみ)

Resources:
  SimpleStateMachine:
    Type: AWS::Serverless::StateMachine
    Properties:
      Definition:
        StartAt: Single State
        States:
          Single State:
            Type: Map
            ItemsPath: "$.Comment"
            Iterator:
              StartAt: CalcSquare
              States:
                CalcSquare:
                  Type: Wait
                  Seconds: 3
                  End: true
            End: true
      Policies:
        - CloudWatchPutMetricPolicy: {}

デプロイするとPassの時と同様にJSON定義が作成されます。

{
    "StartAt": "Single State",
    "States": {
        "Single State": {
            "End": true,
            "ItemsPath": "$.Comment",
            "Iterator": {
                "StartAt": "CalcSquare",
                "States": {
                    "CalcSquare": {
                        "End": true,
                        "Seconds": 3,
                        "Type": "Wait"
                    }
                }
            },
            "Type": "Map"
        }
    }
}

後にWebコンソールから以下のJSONを指定して実行します。配列内部の数字は適当で特に意味は無いです。

{
    "Comment": [12,34,56]
}

実行して数秒すると正常に実行できることが確認できます。

YAML記述に関しては以上です。上記の通りYAML定義がJSON定義に変換されているだけなので、作成したいType内部のJSON階層が分かればYAMLで記述できます。

ASLに記述する場合

ASLで記述する場合は、YAMLのテンプレートからASLを呼び出す必要があります。具体的には以下のような記述で呼び出します。ソースは参考URLの「Definition substitutions」の章からソースの一部を抜き出しています。

template.yaml(一部)

      …
      # 読み込むASLファイルの指定
      DefinitionUri: statemachine/stockTrader.asl.json
      # ASLに渡すテンプレート内の値を指定
      DefinitionSubstitutions:
        StockCheckerFunctionArn: !GetAtt StockCheckerFunction.Arn
      …
  StockCheckerFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/stock-checker/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      …

ASL内部では${StockCheckerFunctionArn}と記述することでYAML内のARNを取得することができます。

statemachine/stock_trader.asl.json

{
    "Comment": "A state machine that does mock stock trading.",
    "StartAt": "Check Stock Value",
    "States": {
        "Check Stock Value": {
            "Type": "Task",
            "Resource": "${StockCheckerFunctionArn}",
            "Retry": [
                {
                    "ErrorEquals": [
                        "States.TaskFailed"
                    ],
                    "IntervalSeconds": 15,
                    "MaxAttempts": 5,
                    "BackoffRate": 1.5
                }
            ],
            "Next": "Check Stock Value2"
        },
        …
}

上記の記述では省きましたが、DynamoDBのテーブル名などを渡すことも可能です。Resource指定以外にも、キックするイベントの指定や実行するワークフローのタイプなどを指定できます。詳細はマニュアル参考URLをご確認ください。

所感

SAMでStep Functionsが対応したことで無理にテンプレートへ組み込む必要がなくなったので、テンプレート内部の記述は減らすことができそうです。SAMでStep Functionsを使う際に参考にしていただければ幸いです。

参考資料

状態 - AWS Step Functions