APIGateway
serverless
ServerlessFramework
stepfunctions
funny

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


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をガリガリ書いていたので特に抵抗感や苦労はありませんでした。。。