最強というかカンタン?
サーバーレスの構成として一般的な
API Gateway&Lambdaですが
API Gatewayは29秒制限、
Lambdaは15分制限があるわけですね
Lambdaの性能を目一杯使いつつ、
そのギャップを埋めるため
なるべくシンプルな構成で非同期APIにしたい ってこと
あると思います。
そのときに必要なのは
- LambdaをキックするためのAPI
- キックしたLambdaが完了したか判断し、レスポンスを受け取るためのAPI
だと思いますが、下記Step Functionsにはその両方が備わっています
Step Functions
Step Functionsでは、
マイクロサービスの連携などワークフローを視覚的でイケメンな感じに
構成することができます。
やばいですね
なんでもできちゃいそうです
TaskとしてLambdaを定義することで
このワークフローにじゃかじゃか組み込んでいけちゃうんですね
ちなみに今回作るのはこれです(かわいい)
API Gatewayをつくる
こんなかんじでつくります
start-executionが上述のLambdaをキックするためのAPI
describe-executionがLambdaからレスポンスを受け取るためのAPI
(名前はなんでもいいです)
両方POSTです
Serverless Framework
んじゃSLSで実際に作っていきます
CFnで作るより格段に楽なんですが、
プラグインとして serverless-step-functions
と serverless-pseudo-parameters
を入れる必要があります
plugins:
- serverless-step-functions
- serverless-pseudo-parameters
関数はこんな感じでつくります
ラーメンタイマーです
provider:
name: aws
runtime: python3.8
stage: ${opt:stage, 'dev'}
region: ap-northeast-1
memorySize: 128
timeout: 900
custom:
basePath: ramen
functions:
async-api:
handler: app.lambda_handler
name: async-api-${self:provider.stage}
environment:
TIMER: 30
import os
from time import sleep
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
def lambda_handler(event, context):
logger.info(f"event: {event}")
seconds = int(os.environ.get("TIMER"))
sleep(seconds)
return {"message": "らーめんが ゆであがりました はやく たべないと のびて しまいます"}
Step Functionsの定義はこんな感じに
serverless-step-functions
プラグインを使う場合
API Gatewayの定義もstateMachines
ブロック内に書きます
stepFunctions:
stateMachines:
state-machine:
name: state-machine-${self:provider.stage}
events:
- http:
path: ${self:custom.basePath}/start-execution
method: post
action: StartExecution
iamRole:
Fn::GetAtt: [AsyncApiRole, Arn]
request:
template:
application/json: |-
{
"input": "$util.escapeJavaScript($input.json('$'))",
"stateMachineArn":"arn:aws:states:#{AWS::Region}:#{AWS::AccountId}:stateMachine:state-machine-${self:provider.stage}"
}
- http:
path: ${self:custom.basePath}/describe-execution
method: post
action: DescribeExecution
iamRole:
Fn::GetAtt: [AsyncApiRole, Arn]
response:
template:
application/json: |-
{
"input": $util.parseJson($input.json('$.input')),
#if($input.path('$.output') != "")
"output": $util.parseJson($input.json('$.output')),
#end
"status": $input.json('$.status')
}
definition:
StartAt: async-api-task
States:
async-api-task:
Type: Task
Resource:
Fn::GetAtt: [async-api, Arn]
End: true
ポインツ
-
start-execution
にはevents.http.action
にStartExecutionを記載-
events.http.request.template
に実行するStateMachineのArnをJson形式で記載
-
-
describe-execution
にはevents.http.action
にDescribeExecutionを記載-
events.http.request.template
にレスポンスをJson形式で記載
-
{
"response": $input.json('$'),
}
とかにすればDescribeExecutionの中身が全部返るんだけど
必要なものだけ返してあげたほうが、インターフェース的にヤサシイでしょう(たぶん)
動かす
じゃあデプロイしてうごかしましょう
sls deploy
っと
コンソールに出たエンドポイントでお試しします
start-execution
これで取得したexecutionArn
をつかって次のAPIを叩きます
なんか返ってきましたね。
status
がRUNNING
です。
まだラーメンできていないようです。
お、status
がSUCCEEDED
になり、ラーメンができあがったことをおしえてくれましたね
以上
いやーめちゃめちゃ楽ですね
これがなかったらDynamoとかSQSとかをフル活用して自前実装しないといけないとこでした(地獄)