Step FunctionsステートマシンでLambda関数の戻り値のペイロードを取得したい場合は関数ハンドラーをasyncで実行しよう

2021.12.16

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

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

今回は、AWS Step FunctionsステートマシンでLambda関数の戻り値のペイロードを取得したい場合は、関数ハンドラーをasyncで実行しよう、という話です。

ステートマシンから実行したLambda関数の戻り値がnullとなる

実行結果を返す次のようなLambda関数のハンドラーコードがあります。

interface Output {
  strValue: string;
}

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

このハンドラーをステートマシンから実行する構成を次の通りAWS CDKスタックで作成します。

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,
        payloadResponseOnly: true,
      }
    );

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

cdk deployでスタックをデプロイしたら、作成されたステートマシンを実行します。

実行が成功しました。

convertValueFormatTaskTaskStateExitedを見ると、Lambda関数の戻り値が入るはずがnullとなってしまっています。

{
  "name": "convertValueFormatTask",
  "output": null,
  "outputDetails": {
    "truncated": false
  }
}

解決

関数ハンドラーをasyncで実行するようにします。

src/lambda/handlers/sampleHandler.ts

interface Output {
  strValue: string;
}

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

cdk deployでスタックをデプロイしたら、作成されたステートマシンを実行します。

実行が成功しました。

convertValueFormatTaskTaskStateExitedを見ると、ちゃんとLambda関数の戻り値が取得できていますね。

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

どういうことなのか?

理由としては、ステートマシンでタスクの実行中に外部処理の実行を待機して、実行結果を取得したい場合はコールバックパターンとする必要があるからです。

Callback tasks provide a way to pause a workflow until a task token is returned. A task might need to wait for a human approval, integrate with a third party, or call legacy systems. For tasks like these, you can pause Step Functions indefinitely, and wait for an external process or workflow to complete.

コールバックタスクは、タスクトークンが返されるまでワークフローを待機させる方法を提供します。タスクには、人間による承認、サードパーティとの統合、あるいはレガシーシステムの呼び出しまで待機することが必要になる場合があります。このようなタスクでは、Step Functions sを無期限に停止して、外部のプロセスまたはワークフローが完了するまで待機させることができます。

Lambda関数ハンドラーでコールバックパターンとしたい場合は、asyncのほかに、callback()を使用する方法もあります。

例えば先程のハンドラーであれば次のように修正できます。(型付けが適当なのは目を瞑ってください)

export const handler = (event: any, context: any, callback: any) => {
  callback(null, {
    strValue: '10',
  });
};

デプロイしてステートマシンから実行します。

すると、ちゃんとLambda関数のペイロード戻り値が取得できました。加えて、payloadResponseOnly: trueであるにも関わらず、メタデータも取得されています。

{
  "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": [
          "eda2c349-52b8-446d-8b9d-6be58b76fd7f"
        ],
        "Content-Length": [
          "17"
        ],
        "Date": [
          "Thu, 16 Dec 2021 14:48:17 GMT"
        ],
        "X-Amzn-Trace-Id": [
          "root=1-61bb51b0-17486cef380c85c825b01ddf;sampled=0"
        ],
        "Content-Type": [
          "application/json"
        ]
      },
      "HttpHeaders": {
        "Connection": "keep-alive",
        "Content-Length": "17",
        "Content-Type": "application/json",
        "Date": "Thu, 16 Dec 2021 14:48:17 GMT",
        "X-Amz-Executed-Version": "$LATEST",
        "x-amzn-Remapped-Content-Length": "0",
        "x-amzn-RequestId": "eda2c349-52b8-446d-8b9d-6be58b76fd7f",
        "X-Amzn-Trace-Id": "root=1-61bb51b0-17486cef380c85c825b01ddf;sampled=0"
      },
      "HttpStatusCode": 200
    },
    "SdkResponseMetadata": {
      "RequestId": "eda2c349-52b8-446d-8b9d-6be58b76fd7f"
    },
    "StatusCode": 200
  },
  "outputDetails": {
    "truncated": false
  }
}

まとめ

今回の検証をまとめると次のようになりました。Lambda関数側での実装によって戻り値を制御できることがわかりました。

  • ステートマシンからLambda関数を実行した際のハンドラーの実装による戻り値の違い
non-async async callback
ペイロード なし あり あり
メタデータ なし なし あり

この違いを把握できていないとハマることもありそうなので確認出来てよかったです。

以上