[AWS CDK] Step FunctionsステートマシンでLambdaInvokeでの”payloadResponseOnly”の指定による動作の違いを確認してみた

2021.12.15

こんにちは、CX事業本部 IoT事業部の若槻です。

今回は、AWS CDKでのAWS Step Functionsステートマシンの構築で、LambdaInvokeでのpayloadResponseOnlyの指定による動作の違いを確認してみました。

LambdaInvokeとは

ステートマシンからLambda関数を実行するタスクをAWS CDKで実装したい場合、CallAwsServiceまたはLambdaInvokeのコンストラクトを使用します。

CallAwsServiceは、AWSサービスのAPIを汎用的に叩けるコンストラクトです。

LambdaInvokeは、Lambda関数を実行するためのコンストラクトです。

LambdaInvokeの方が高レベルで使用可能なプロパティが限られていますが、大抵の場合はこちらの使用で対応可能です。

そしてこのコンストラクトにはpayloadResponseOnlyというプロパティがあります。指定はオプションで、既定ではfalseとなります。

payloadResponseOnly?
Type: boolean (optional, default: false)

Invoke the Lambda in a way that only returns the payload response without additional metadata.

The payloadResponseOnly property cannot be used if integrationPattern, invocationType, clientContext, or qualifier are specified. It always uses the REQUEST_RESPONSE behavior.

今回はこのpayloadResponseOnlytrue/falseの指定による動作の違いを確認してみます。

確認してみた

payloadResponseOnly:falseの場合

まずpayloadResponseOnlyがfalseの場合の動作についてです。

次の通りCDKスタックを作成しました。LambdaInvokeコンストラクトでpayloadResponseOnlyプロパティでfalseを指定しています。

lib/aws-cdk-app-stack.ts

import * as cdk from '@aws-cdk/core';
import * as lambdaNodejs from '@aws-cdk/aws-lambda-nodejs';
import * as sfn from '@aws-cdk/aws-stepfunctions';
import * as tasks from '@aws-cdk/aws-stepfunctions-tasks';

export class AwsCdkAppStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const convertValueFormatFunc = new lambdaNodejs.NodejsFunction(
      this,
      'convertValueFormatFunc',
      {
        functionName: 'convertValueFormatFunc',
        entry: 'src/lambda/handlers/sampleHandler.ts',
      }
    );

    const convertValueFormatTask = new tasks.LambdaInvoke(
      this,
      'convertValueFormatTask',
      {
        lambdaFunction: convertValueFormatFunc,
        payload: sfn.TaskInput.fromJsonPathAt('$'),
        payloadResponseOnly: false //未指定なら既定でfalseとなる
      }
    );

    new sfn.StateMachine(this, 'testStateMachine', {
      stateMachineName: 'testStateMachine',
      definition: convertValueFormatTask,
    });
  }
}

Lambda関数のコードは次の通りです。入力されたnumValueプロパティの値を文字列に変換してstrValueプロパティの値としてReturnしています。

src/lambda/handlers/sampleHandler.ts

interface Event {
  numValue: number;
}

interface Output {
  strValue: string;
}

export const handler = async (event: Event): Promise<Output> => {
  return {
    strValue: event.numValue.toString(),
  };
};

cdk deployでデプロイしてステートマシンを作成します。

作成されたステートマシンの定義は下記のようになります。

{
  "StartAt": "convertValueFormatTask",
  "States": {
    "convertValueFormatTask": {
      "End": true,
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException"
          ],
          "IntervalSeconds": 2,
          "MaxAttempts": 6,
          "BackoffRate": 2
        }
      ],
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:convertValueFormatFunc",
        "Payload.$": "$"
      }
    }
  }
}

このステートマシンに次の入力を指定して実行します。

{
    "numValue": 10
}

実行が成功しました。実行結果の履歴は次の通りです。

convertValueFormatTaskTaskScheduled(タスクの入力)は次のようになりました。Lambdaの入力ペイロードに、ステートマシンの入力の値が指定できています。

{
  "resourceType": "lambda",
  "resource": "invoke",
  "region": "ap-northeast-1",
  "parameters": {
    "FunctionName": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:convertValueFormatFunc",
    "Payload": {
      "numValue": 10
    }
  },
  "timeoutInSeconds": null,
  "heartbeatInSeconds": null
}

またconvertValueFormatTaskTaskStateExited(タスクの出力)は次のようになりました。Lambda関数の戻り値のペイロードのパスはoutput.Payloadとなっています。また戻り値以外に様々なメタデータが取得できています。

{
  "name": "convertValueFormatTask",
  "output": {
    "ExecutedVersion": "$LATEST",
    "Payload": {
      "strValue": "10"
    },
    "SdkHttpMetadata": {
      "AllHttpHeaders": {
        "X-Amz-Executed-Version": [
          "$LATEST"
        ],
        "x-amzn-Remapped-Content-Length": [
          "0"
        ],
        "Connection": [
          "keep-alive"
        ],
        "x-amzn-RequestId": [
          "0bb36b3d-f415-46bb-a540-bffa15d86e4d"
        ],
        "Content-Length": [
          "17"
        ],
        "Date": [
          "Wed, 15 Dec 2021 14:59:04 GMT"
        ],
        "X-Amzn-Trace-Id": [
          "root=1-61ba02b8-2e7068885bd4927a41f93ee8;sampled=0"
        ],
        "Content-Type": [
          "application/json"
        ]
      },
      "HttpHeaders": {
        "Connection": "keep-alive",
        "Content-Length": "17",
        "Content-Type": "application/json",
        "Date": "Wed, 15 Dec 2021 14:59:04 GMT",
        "X-Amz-Executed-Version": "$LATEST",
        "x-amzn-Remapped-Content-Length": "0",
        "x-amzn-RequestId": "0bb36b3d-f415-46bb-a540-bffa15d86e4d",
        "X-Amzn-Trace-Id": "root=1-61ba02b8-2e7068885bd4927a41f93ee8;sampled=0"
      },
      "HttpStatusCode": 200
    },
    "SdkResponseMetadata": {
      "RequestId": "0bb36b3d-f415-46bb-a540-bffa15d86e4d"
    },
    "StatusCode": 200
  },
  "outputDetails": {
    "truncated": false
  }
}

payloadResponseOnly:trueの場合

次にpayloadResponseOnlyがtrueの場合の動作についてです。

LambdaInvokeコンストラクトでpayloadResponseOnlyプロパティをtrue`に修正します。

lib/aws-cdk-app-stack.ts

    const convertValueFormatTask = new tasks.LambdaInvoke(
      this,
      'convertValueFormatTask',
      {
        lambdaFunction: convertValueFormatFunc,
        payload: sfn.TaskInput.fromJsonPathAt('$'),
        payloadResponseOnly: true
      }
    );

Lambda関数は修正しないでおきます。cdk deployでタスクの変更をデプロイします。

作成されたステートマシンの定義は下記のようになります。payloadResponseOnly:falseの場合とdiffで比較すると、ResourceParametersの指定が異なっています。

{
  "StartAt": "convertValueFormatTask",
  "States": {
    "convertValueFormatTask": {
      "End": true,
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException"
          ],
          "IntervalSeconds": 2,
          "MaxAttempts": 6,
          "BackoffRate": 2
        }
      ],
      "Type": "Task",
+      "Resource": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:convertValueFormatFunc",
+      "Parameters": "$"
-      "Resource": "arn:aws:states:::lambda:invoke",
-      "Parameters": {
-        "FunctionName": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:convertValueFormatFunc",
-        "Payload.$": "$"
-      }
    }
  }
}

このステートマシンに先程と同じく次の入力を指定して実行します。

{
    "numValue": 10
}

実行が失敗しました。実行結果の履歴は次の通りです。

convertValueFormatTaskTaskScheduled(タスクの入力)は次のようになりました。Lambdaの入力ペイロードで$が解決できておらず、ステートマシンの入力の値が指定できていません。

{
  "resource": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:convertValueFormatFunc",
  "input": "$",
  "inputDetails": {
    "truncated": false
  },
  "timeoutInSeconds": null
}

またconvertValueFormatTaskTaskStateExited(タスクの出力)は次のようになりました。案の定Lambda関数の実行がエラーとなっています。

{
  "error": "TypeError",
  "cause": {
    "errorType": "TypeError",
    "errorMessage": "Cannot read property 'toString' of undefined",
    "trace": [
      "TypeError: Cannot read property 'toString' of undefined",
      "    at Runtime.handler (/var/task/index.js:15:30)",
      "    at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"
    ]
  }
}

payloadResponseOnly:trueの場合はLambdaの入力ペイロードを指定できないようです。

そこでLambda関数で入力は使用せず、出力の値を取得するのみの構成としてみます。

まずLambdaInvokeコンストラクトでpayloadプロパティを削除します。

lib/aws-cdk-app-stack.ts

    const convertValueFormatTask = new tasks.LambdaInvoke(
      this,
      'convertValueFormatTask',
      {
        lambdaFunction: convertValueFormatFunc,
        payloadResponseOnly: true,
      }
    );

そしてLambda関数のコードを入力を使用しない記述とします。

interface Output {
  strValue: string;
}

export const handler = async (): Promise<Output> => {
  return {
    strValue: '10',
  };
};

変更をデプロイしてステートマシンを実行します。

実行が成功しました。実行結果の履歴は次の通りです。

convertValueFormatTaskTaskStateExited(タスクの出力)は次のようになりました。Lambda関数の戻り値のペイロードのパスはoutputとなっています。またメタデータなどのペイロード以外のデータは出力されていません。

{
  "name": "convertValueFormatTask",
  "output": {
    "strValue": "10"
  },
  "outputDetails": {
    "truncated": false
  }
}

まとめ

LambdaInvokeコンストラクトでのpayloadResponseOnlyの指定の違いをまとめると次のようになりました。

false true
入力ペイロードの指定 可能 不可
出力ペイロードのパス output.Payload output
出力メタデータ あり なし

Lambda関数で入力ペイロードに応じた処理を行いたい場合はpayloadResponseOnlytrue、そうでない場合はfalseと指定すれば良さそうですね。

参考

以上