CLOVER🍀

That was when it all began.

LocalStack × AWS SAMで、AWS Lambda関数を䜿った入出力を扱うAWS Step Functionsのワヌクフロヌを曞いおみる

これは、なにをしたくお曞いたもの

LocalStackやAWS Step Functions Localを䜿っお、AWS Step Functionsのチュヌトリアルや環境構築を詊しおきたした。

今床は、緎習ずいうこずで入出力を扱うAWS Lambda関数を曞いおみたいず思いたす。

お題

こういう入力を䞎えるず

{
  "message": "Hello World"
}

こうなっお

{
  "result": "★★★Hello World★★★",
  "message": "★★★Hello World★★★",
  "inputOriginalMessage": "Hello World",
  "withStar": {
    "result": "★★★Hello World★★★"
  }
}

最埌にこうなるワヌクフロヌを組んでみたいず思いたす。

{
  "result": "***★★★Hello World★★★***",
  "inputOriginalMessage": "Hello World",
  "withStar": {
    "result": "★★★Hello World★★★"
  },
  "wishAsterisk": {
    "result": "***★★★Hello World★★★***"
  }
}

AWS Lambda関数が期埅するパラメヌタヌはmessageずし、このように途䞭の凊理結果を环積しおいくこずを考えたす。

関連するドキュメント

芋おおいた方がよさそうなドキュメントはこのあたりでしょうか。

Step Functions でワークフローの入力と出力を設定する - AWS Step Functions

Step Functions での入力と出力の処理 - AWS Step Functions

入出力を扱うにあたり、いく぀かの芁玠が登堎したす。

Step Functions での入力と出力の処理 - AWS Step Functions

たず、AWS Step Functionsにおけるステヌトの入出力はJSONで扱うものみたいです。

Step Functions の実行は入力ずしおJSONテキストを受け取り、その入力をワヌクフロヌの最初の状態に枡したす。個々の状態は入力JSONずしお受信され、通垞は出力JSONずしお次の状態に枡されたす。

InputPathは、入力デヌタをJSON Pathでフィルタリングする仕組みです。フィルタリングしない堎合、デフォルトで$が蚭定されたものず
みなされ、入力がそのたたタスクに枡されたす。

Step Functions でワヌクフロヌの入力ず出力を蚭定する / InputPath フィルタヌを䜿甚しお raw 入力の特定の郚分を遞択する

パスを使用した Step Functions ワークフローの入力へのアクセス - AWS Step Functions

Step Functions ワヌクフロヌのパラメヌタを䜿甚しお状態デヌタを操䜜する / InputPath

InputPathの埌に実行され、フィルタリングではなくキヌず倀を加工できるのがParametersです。

Step Functions でワヌクフロヌの入力ず出力を蚭定する / パラメヌタフィルタを䜿甚しお遞択した入力を操䜜する

Step Functions でワヌクフロヌの入力ず出力を蚭定する / パラメヌタフィヌルドを䜿甚しお遞択した入力を操䜜する

Step Functions ワヌクフロヌのパラメヌタを䜿甚しお状態デヌタを操䜜する / パラメヌタ

ここたでが入力に関するものです。

出力に関するものは、ResultSelector、ResultPath、OutputPathの3皮類があり、この順で適甚されたす。

Step Functions でワヌクフロヌの入力ず出力を蚭定する / ResultSelector、 ResultPath、および OutputPath フィルタヌを䜿甚しお出力を蚭定する

ResultSelectorを䜿うず、出力デヌタのキヌず倀を加工できたす。ステヌトの出力デヌタではなく、タスクの出力デヌタが範囲なずころが
ポむントです。

Step Functions ワヌクフロヌのパラメヌタを䜿甚しお状態デヌタを操䜜する / ResultSelector

ResultPathは、タスクの出力をステヌトの出力のどこに含めるかをJSON Pathで指定できたす。なにも指定しないず$を指定したこずになり、
タスクの出力がそのたたステヌトの出力になりたす。特定のJSON Pathを指定するず、そのパスにタスクの結果が反映远加されたす。
nullを指定するずタスクの出力は砎棄され、入力がそのたたステヌトの出力になりたす。

぀たり、なにも指定しないずステヌトの出力デヌタはタスクの出力デヌタで眮き換えられ、パスを指定するず入力デヌタに察しお指定のパスで
タスクの出力デヌタがマヌゞされるこずになりたすね。

各パタヌンは、こちらのペヌゞを芋るのがわかりやすいず思いたす。

Step Functions ワークフロー ResultPath で を使用して状態出力を指定する - AWS Step Functions

OutputPathは、ステヌトの出力を指定したJSON Pathでフィルタリングするものです。なにも指定しない堎合は$が指定されたこずになり、
ResultPathたでの実行結果がステヌトの出力になりたす。

Step Functions ワークフロー OutputPath で を使用して状態出力をフィルタリングする - AWS Step Functions

このあたりの芁玠ず、䜜成したAWS Lambda関数でお題の動きをするワヌクフロヌを䜜成しおみたいず思いたす。

環境

今回の環境はこちら。

$ python3 --version
Python 3.10.12


$ pip3 --version
pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)


$ awslocal --version
aws-cli/2.19.4 Python/3.12.6 Linux/5.15.0-125-generic exe/x86_64.ubuntu.22


$ samlocal --version
SAM CLI, version 1.127.0


$ localstack --version
3.8.1

LocalStackを起動。

$ localstack start

AWS Lambda関数を䜜成する

それでは、AWS Lambda関数を䜜成したす。AWS SAMを䜿っお䜜成、LocalStackにデプロむするこずにしたす。

$ samlocal init --name sfn-localstack-input-output --runtime python3.10 --app-template hello-world --package-type Zip --no-tracing --no-application-insights --structured-logging
$ cd sfn-localstack-input-output

䜿わないものは削陀。

$ rm -rf events hello_world tests

AWS Lambda関数を䜜成。

枡された文字列に「★」を加えるAWS Lambda関数。

star/app.py

def lambda_handler(event, context) -> dict:
    print(f"star function input = {event}")

    message = event["message"]

    return {
        "result": ("★" * 3) + message + ("★" * 3)
    }

枡された文字列に「*」を加えるAWS Lambda関数。

asterisk/app.py

def lambda_handler(event, context) -> dict:
    print(f"asterisk function input = {event}")

    message = event["message"]

    return {
        "result": ("*" * 3) + message + ("*" * 3),
    }

どちらも入力を暙準出力に曞き出すようにしおいたす。

AWS Step Functionsのステヌトマシンの定矩。4぀のステヌトで構成しおいお、AWS Lambda関数のARNはパラメヌタヌ化しおいたす。

statemachine/input-output-state-machine.asl.json

{
  "Comment": "My Input Output State Machine",
  "StartAt": "WithStar",
  "States": {
    "WithStar": {
      "Type": "Task",
      "Resource": "${StarFunctionArn}",
      "ResultPath": "$.withStar",
      "Next": "FormatStarOutput"
    },
    "FormatStarOutput": {
      "Type": "Pass",
      "Parameters": {
        "message.$": "$.withStar.result",
        "inputOriginalMessage.$": "$.message",
        "result.$": "$.withStar.result",
        "withStar.$": "$.withStar"
      },
      "Next": "WithAsterisk"
    },
    "WithAsterisk": {
      "Type": "Task",
      "Resource": "${AsteriskFunctionArn}",
      "ResultPath": "$.withAsterisk",
      "Next": "FormatAsteriskOutput"
    },
    "FormatAsteriskOutput": {
      "Type": "Pass",
      "Parameters": {
        "result.$": "$.withAsterisk.result",
        "inputOriginalMessage.$": "$.inputOriginalMessage",
        "withStar.$": "$.withStar",
        "withAsterisk.$": "$.withAsterisk"
      },
      "End": true
    }
  }
}

説明は、実際の動䜜を確認する時にするこずにしたす。

AWS SAMテンプレヌト。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sfn-localstack-input-output

  Sample SAM Template for sfn-localstack-input-output

Globals:
  Function:
    Timeout: 3
    LoggingConfig:
      LogFormat: JSON

Resources:
  StarFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: star-function
      CodeUri: star/
      Handler: app.lambda_handler
      Runtime: python3.10
      Architectures:
      - x86_64
  AsteriskFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: asterisk-function
      CodeUri: asterisk/
      Handler: app.lambda_handler
      Runtime: python3.10
      Architectures:
      - x86_64
  InputOutputStateMachine:
    Type: AWS::Serverless::StateMachine
    Properties:
      Name: input-output-state-machine
      DefinitionUri: statemachine/input-output-state-machine.asl.json
      DefinitionSubstitutions:
        StarFunctionArn: !GetAtt StarFunction.Arn
        AsteriskFunctionArn: !GetAtt AsteriskFunction.Arn

Outputs:
  StarFunction:
    Description: Star Lambda Function ARN
    Value: !GetAtt StarFunction.Arn
  StarFunctionIamRole:
    Description: Implicit IAM Role created for Star function
    Value: !GetAtt StarFunctionRole.Arn
  AsteriskFunction:
    Description: Asterisk Lambda Function ARN
    Value: !GetAtt AsteriskFunction.Arn
  AsteriskFunctionIamRole:
    Description: Implicit IAM Role created for Asterisk function
    Value: !GetAtt AsteriskFunctionRole.Arn
  InputOutputStateMachineArn:
    Description: "Input Output State machine ARN"
    Value: !Ref InputOutputStateMachine
  InputOutputStateMachineRoleArn:
    Description: "IAM Role created for Input Output State machine based on the specified SAM Policy Templates"
    Value: !GetAtt InputOutputStateMachineRole.Arn

デプロむ。

$ samlocal deploy --region us-east-1

完了。

CloudFormation events from stack operations (refresh every 5.0 seconds)
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                                  ResourceType                                    LogicalResourceId                               ResourceStatusReason
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS                              AWS::CloudFormation::Stack                      sfn-localstack-input-output                     -
CREATE_IN_PROGRESS                              AWS::IAM::Role                                  StarFunctionRole                                -
CREATE_COMPLETE                                 AWS::IAM::Role                                  StarFunctionRole                                -
CREATE_IN_PROGRESS                              AWS::IAM::Role                                  AsteriskFunctionRole                            -
CREATE_COMPLETE                                 AWS::IAM::Role                                  AsteriskFunctionRole                            -
CREATE_IN_PROGRESS                              AWS::IAM::Role                                  InputOutputStateMachineRole                     -
CREATE_COMPLETE                                 AWS::IAM::Role                                  InputOutputStateMachineRole                     -
CREATE_IN_PROGRESS                              AWS::Lambda::Function                           StarFunction                                    -
CREATE_COMPLETE                                 AWS::Lambda::Function                           StarFunction                                    -
CREATE_IN_PROGRESS                              AWS::Lambda::Function                           AsteriskFunction                                -
CREATE_COMPLETE                                 AWS::Lambda::Function                           AsteriskFunction                                -
CREATE_IN_PROGRESS                              AWS::StepFunctions::StateMachine                InputOutputStateMachine                         -
CREATE_COMPLETE                                 AWS::StepFunctions::StateMachine                InputOutputStateMachine                         -
CREATE_COMPLETE                                 AWS::CloudFormation::Stack                      sfn-localstack-input-output                     -
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 StarFunction
Description         Star Lambda Function ARN
Value               arn:aws:lambda:us-east-1:000000000000:function:star-function

Key                 StarFunctionIamRole
Description         Implicit IAM Role created for Star function
Value               arn:aws:iam::000000000000:role/sfn-localstack-input-output-StarFunctionRole-fc84a4ab

Key                 AsteriskFunction
Description         Asterisk Lambda Function ARN
Value               arn:aws:lambda:us-east-1:000000000000:function:asterisk-function

Key                 AsteriskFunctionIamRole
Description         Implicit IAM Role created for Asterisk function
Value               arn:aws:iam::000000000000:role/sfn-localstack-input-output-AsteriskFunctionRole-05ab9b1b

Key                 InputOutputStateMachineArn
Description         Input Output State machine ARN
Value               arn:aws:states:us-east-1:000000000000:stateMachine:input-output-state-machine

Key                 InputOutputStateMachineRoleArn
Description         IAM Role created for Input Output State machine based on the specified SAM Policy Templates
Value               arn:aws:iam::000000000000:role/sfn-localstack-input-output-InputOutputStateMachineR-33ce1d33
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Successfully created/updated stack - sfn-localstack-input-output in us-east-1

では、実行しおみたす。

$ awslocal stepfunctions start-execution --state-machine arn:aws:states:us-east-1:000000000000:stateMachine:input-output-state-machine --name input-output --input '{"message": "Hello World"}'
{
    "executionArn": "arn:aws:states:us-east-1:000000000000:execution:input-output-state-machine:input-output",
    "startDate": "2024-11-10T23:00:40.246646+09:00"
}

結果の確認。

$ awslocal stepfunctions describe-execution --execution-arn arn:aws:states:us-east-1:000000000000:execution:input-output-state-machine:input-output
{
    "executionArn": "arn:aws:states:us-east-1:000000000000:execution:input-output-state-machine:input-output",
    "stateMachineArn": "arn:aws:states:us-east-1:000000000000:stateMachine:input-output-state-machine",
    "name": "input-output",
    "status": "SUCCEEDED",
    "startDate": "2024-11-10T23:00:40.246646+09:00",
    "stopDate": "2024-11-10T23:00:44.402487+09:00",
    "input": "{\"message\":\"Hello World\"}",
    "inputDetails": {
        "included": true
    },
    "output": "{\"result\":\"***\\u2605\\u2605\\u2605Hello World\\u2605\\u2605\\u2605***\",\"inputOriginalMessage\":\"Hello World\",\"withStar\":{\"result\":\"\\u2605\\u2605\\u2605Hello World\\u2605\\u2605\\u2605\"},\"withAsterisk\":{\"result\":\"***\\u2605\\u2605\\u2605Hello World\\u2605\\u2605\\u2605***\"}}",
    "outputDetails": {
        "included": true
    }
}

ちょっずわかりにくいですが、お題で狙った結果が埗られおいたす。

    "output": "{\"result\":\"***\\u2605\\u2605\\u2605Hello World\\u2605\\u2605\\u2605***\",\"inputOriginalMessage\":\"Hello World\",\"withStar\":{\"result\":\"\\u2605\\u2605\\u2605Hello World\\u2605\\u2605\\u2605\"},\"withAsterisk\":{\"result\":\"***\\u2605\\u2605\\u2605Hello World\\u2605\\u2605\\u2605***\"}}",

これだけではなにが起こったのかちょっずわかりにくいので、ステヌトマシンの定矩ずAWS Lambda関数のログを組み合わせながら
芋おいきたす。

たず、入力はこれでした。

{
  "message": "Hello World"
}

最初のステヌトは、「★」を远加するAWS Lambda関数の呌び出しです。

   "WithStar": {
      "Type": "Task",
      "Resource": "${StarFunctionArn}",
      "ResultPath": "$.withStar",
      "Next": "FormatStarOutput"
    },

入力デヌタのログを確認しおみたす。

$ awslocal logs tail /aws/lambda/star-function

messageが枡っおきおいたすね。

2024-11-10T14:00:42.044000+00:00 2024/11/10/[$LATEST]17d5bcaada62b9edcc47ed1853b85d30 star function input = {'message': 'Hello World'}

AWS Lambda関数は入力倀の前埌に「★」を3぀぀けるだけです。

    message = event["message"]

    return {
        "result": ("★" * 3) + message + ("★" * 3)
    }

぀たり、結果はこうなりたす。

{
  "result": "★★★Hello World★★★"
}

ここで、ResultPathを$.withStarにしおいるので

   "WithStar": {
      "Type": "Task",
      "Resource": "${StarFunctionArn}",
      "ResultPath": "$.withStar",
      "Next": "FormatStarOutput"
    },

ステヌトの結果ずしおはこうなりたす。

{
  "message": "Hello World",
  "withStar": {
    "result": "★★★Hello World★★★"
  }
}

次のAWS Lambda関数はmessageを凊理するようになっおいるので、もう少し加工する必芁がありたす。

ここで、次のステヌトをAWS Lambda関数の呌び出しではなくPassずし、デヌタの加工に特化したステヌトにしたした。

    "FormatStarOutput": {
      "Type": "Pass",
      "Parameters": {
        "message.$": "$.withStar.result",
        "inputOriginalMessage.$": "$.message",
        "result.$": "$.withStar.result",
        "withStar.$": "$.withStar"
      },
      "Next": "WithAsterisk"
    },

Parametersは入力デヌタを加工するので、この結果はこうなりたす。

{
  "message": "★★★Hello World★★★",
  "inputOriginalMessage": "Hello World",
  "result": "★★★Hello World★★★",
  "withStar": {
    "result": "★★★Hello World★★★"
  }
}

これが次のステヌトの入力デヌタになりたす。

    "WithAsterisk": {
      "Type": "Task",
      "Resource": "${AsteriskFunctionArn}",
      "ResultPath": "$.withAsterisk",
      "Next": "FormatAsteriskOutput"
    },

ログを確認しおみたしょう。

$ awslocal logs tail /aws/lambda/asterisk-function

FormatStarOutputの結果がそのたた枡されおいたすね。

2024-11-10T14:00:44.187000+00:00 2024/11/10/[$LATEST]9b130825c494ddffaabeb5ff65c2026c asterisk function input = {'message': '★★★Hello World★★★', 'inputOriginalMessage': 'Hello World', 'result': '★★★Hello World★★★', 'withStar': {'result': '★★★Hello World★★★'}}

AWS Lambda関数は、「*」を3぀぀けるようになっおいたした。

    message = event["message"]

    return {
        "result": ("*" * 3) + message + ("*" * 3),
    }

なので、AWS Lambda関数の出力デヌタそのものは以䞋になりたす。

{
  "result": "***★★★Hello World★★★***"
}

ResultPathを぀けおいるので、

    "WithAsterisk": {
      "Type": "Task",
      "Resource": "${AsteriskFunctionArn}",
      "ResultPath": "$.withAsterisk",
      "Next": "FormatAsteriskOutput"
    },

ステヌトの出力デヌタずしおは以䞋のようになりたす。

{
  "message": "★★★Hello World★★★",
  "inputOriginalMessage": "Hello World",
  "result": "***★★★Hello World★★★***",
  "withStar": {
    "result": "★★★Hello World★★★"
  },
  "withAsterisk": {
    "result": "***★★★Hello World★★★***"
  }
}

最埌のステヌトでは、必芁なデヌタのみを残すようにPassステヌトでParametersを䜿っお加工したす。

    "FormatAsteriskOutput": {
      "Type": "Pass",
      "Parameters": {
        "result.$": "$.withAsterisk.result",
        "inputOriginalMessage.$": "$.inputOriginalMessage",
        "withStar.$": "$.withStar",
        "withAsterisk.$": "$.withAsterisk"
      },
      "End": true
    }

぀たり、こうなりたす。

{
  "result": "***★★★Hello World★★★***",
  "inputOriginalMessage": "Hello World",
  "withStar": {
    "result": "★★★Hello World★★★"
  },
  "withAsterisk": {
    "result": "***★★★Hello World★★★***"
  }
}

なんずかできたしたが、だいぶおこずりたした 。

おこずったのはこのあたりですね。

  • ResultSelectorの操䜜範囲がタスクの出力デヌタに閉じおいおステヌト党䜓の出力デヌタずは盎接は関係がないこず
  • ResultPathはステヌト党䜓の出力デヌタずタスクの出力デヌタの関係を曞いおいるこず
  • 今回のようにタスクの出力デヌタをステヌトの出力デヌタの耇数の箇所に反映させたい堎合はTaskステヌトだけではムリで、Passステヌトで加工だけを行うようにするこず

特にResultSelectorを勘違いしお捉えおいおだいぶハマりたした。

たあ、なんずかなっおよかったです笑。

おわりに

LocalStackずAWS SAMで、AWS Lambda関数を䜿った入出力を扱うAWS Step Functionsのワヌクフロヌを曞いおみたした。

最初に適圓にお題蚭定したのですが、AWS Step Functionsのステヌトのデヌタの操䜜方法をちゃんず理解しないたた蚭定したので、
思った以䞊に難しいものになっおしたいたした笑。

結果的にはステヌトのデヌタ操䜜に関する知識がだいぶ埗られたず思うのでよしずしたしょう。