LoginSignup
19
14

More than 3 years have passed since last update.

API GatewayでStepFunctionsと統合する時のTips

Posted at

APIGatewayのStepFunctions Integration

API Gatewayの統合タイムアウトの最大は29秒なので、バッチ等の時間がかかる処理を行うAPIを作成する場合は統合先にLambdaではなくStepFunctionsを設定し、StartExecutionだけのレスポンスを得て裏で処理を継続させるパターンがあると思います。

参考資料:
【API Gatewayタイムアウト対策】Step Functionsを組み合わせて非同期処理にしてみる

MappingTemplate

StartExecutionのAPIを叩く場合、リクエストのpayloadにはStepFunctionsで使うInputとMachineARNを付与します。
Inputは、エスケープされたJSONが必要ですが、これはMappingTemplateで処理可能です。

公式ドキュメントにサンプルが載っています

必要なリクエストbody

{
   "input": "{}",
   "name": "MyExecution",
   "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld"
}

公式のサンプルMappingTemplate

{
    "input": "$util.escapeJavaScript($input.json('$'))",
    "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld"
} 

$util.escapeJavaScriptを使ってリクエストbodyのJSONをエスケープしています。
また、stateMachineArnを指定しているのでリクエストに含める必要がなくなります。
したがって、このMappingTemplateを適用した場合のリクエストはinputそのものになります。
name属性はExecutionNameなので90日間ステートマシン内で一意である必要があるんですが、
指定しなければAPIGatewayが生成するRequestIdがそのまま使われるようです。

RequestBody
{
   "input_key": "input_value"
}
変換後のbody
{
   "input": "{\"input_key\" : \"input_value\"}",
   "name": "requestId",
   "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld"
}

inputにrequestContextを含みたい!

サンプルの構成のだとステートマシンに与えられるinputはリクエストbodyのみです。
つまりStepfunctionから呼ばれるLambdaはcontextを参照できません。
それでは少し不便なのでMappingTemplateを少し改良してRequestIdをinputに含めてみましょう。

MapingTemplate
#set( $body = $util.escapeJavaScript($input.json('$')) )
{
  "input": "{\"input\":$body,\"requestContext\":{\"requestId\":\"$context.requestId\"}}",
  "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld"
}

#set( $body = $util.escapeJavaScript($input.json('$')) )$bodyにinput jsonを代入します。
$contextでリクエスト時のcontextにアクセスできます。
$context.requestIdとすることで実際のRequestIdを値としてセットしています。
あとは忘れないようにinputの中をエスケープしましょう。
これを忘れるとInternal ServerErrorになってしまいます(泣)

さっきと同じようにリクエストしてみます。

RequestBody
{
   "input_key": "input_value"
}
変換後のbody
{
  "input": "{\"input\":{\"input_key\":\"input_value\"}, \"requestContext\":\"requestId\"}",
  "name": "requestId",
  "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld"
}

これでステートマシン内のFunctionからRequestIdが拾えるようになりました。

Serverless Frameworkでのデプロイ

AWS SAMでも良いのですがdefinitionの記述だけJSONになってしまうのでこちらを選択しました。
serverless-step-functionsというプラグインを使うことでデプロイ可能です。

serverless.yml
plugins:
  - serverless-step-functions

functions:
  hello:
    handler: handler.hello

stepFunctions:
  stateMachines:
    MyStateMachine:
      name: myStateMachine
      events:
        - http:
            path: /execute
            method: post
            request:
              template:
                application/json: |
                  #set( $body = $util.escapeJavaScript($input.json('$')) )
                  {
                    "input": "{\"input\":$body,\"requestContext\":{\"requestId\":\"$context.requestId\"}}",
                    "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld"
                  }
      definition:
        Comment: "serverless"
        StartAt: HelloWorld1
        States:
          HelloWorld1:
            Type: Task
            Resource:
              Fn::GetAtt: [HelloLambdaFunction, Arn]
            End: true

providerは省略しています。
こんな感じで簡単にステートマシンとAPIGatewayを作成できます。
基本的な使い方は大体readmeに書いてあり始めやすいです。
非常に使いやすいのですが、構成次第ではちょっと工夫が必要になります。
例えば、

  • StartExecution以外のAPI叩きたいとき

デフォルトがStartExecutionになっているようで、Describeを叩きたい場合はResourcesでオーバーライドする必要があります

  • APIKeyRequireがデフォルトでオフ

APIKeyの作成は問題なくできますが、メソッドに対する必須条件をつける際はこれもResourcesでオーバーライドしましょう。

  • 2つめのAPIGatewayリソースを作成した場合1つ目のMappingTemplateがコピーされる(気がする)

例えばこのような定義をしたとき、

stepFunctions:
  stateMachines:
    MyStateMachine:
      name: myStateMachine
      events:
        - http:
            path: /execute
            method: post
            request:
              template:
                application/json: |
                  #set( $body = $util.escapeJavaScript($input.json('$')) )
                  {
                    "input": "{\"input\":$body,\"requestContext\":{\"requestId\":\"$context.requestId\"}}",
                    "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld"
                  }
        - http:
            path: /describe
            method: post

これをデプロイすると、
/executeのtemplateが/describeにも装備されてしまうような動作をしました。
これもResourcesで上手くやれば解決します。
あとはMethodResponseとかいろいろありますがServerlessFrameworkのいいところは
pluginが対応していなくてもResourcesで定義してやればなんとかなるところですね。
その分記述量は増えてしまいますが、筆者はよく生のCloudformation templateをガリガリ書いていたので特に抵抗感や苦労はありませんでした。。。

19
14
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
14