TL; DR
AWS SAMを利用してLambdaをこねこねしたことがなかったので、テストなども交えつつやってみようと思います。
今回は簡単なbotをGoを利用して作ってみたいと思います。
Tutorial
公式ドキュメントのチュートリアルをサクッとやっていきます。
# インストール
$ pip install aws-sam-cli
$ sam --version
SAM CLI, version 0.37.0
# 初期化
$ sam init --runtime go1.x --name go-api
$ tree go-api
go-api
├── Makefile
├── README.md
├── hello-world
│ ├── main.go
│ └── main_test.go
└── template.yaml
// 初期ファイル
package main
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"github.com/aws/aws-lambda-go/events" // パッケージが必要
"github.com/aws/aws-lambda-go/lambda" // パッケージが必要
)
var (
// DefaultHTTPGetAddress Default Address
DefaultHTTPGetAddress = "https://checkip.amazonaws.com"
// ErrNoIP No IP found in response
ErrNoIP = errors.New("No IP in HTTP response")
// ErrNon200Response non 200 status code in response
ErrNon200Response = errors.New("Non 200 Response found")
)
func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
resp, err := http.Get(DefaultHTTPGetAddress)
if err != nil {
return events.APIGatewayProxyResponse{}, err
}
if resp.StatusCode != 200 {
return events.APIGatewayProxyResponse{}, ErrNon200Response
}
ip, err := ioutil.ReadAll(resp.Body)
if err != nil {
return events.APIGatewayProxyResponse{}, err
}
if len(ip) == 0 {
return events.APIGatewayProxyResponse{}, ErrNoIP
}
return events.APIGatewayProxyResponse{
Body: fmt.Sprintf("Hello, %v", string(ip)),
StatusCode: 200,
}, nil
}
func main() {
lambda.Start(handler)
}
# パッケージインストール
$ go get "github.com/aws/aws-lambda-go/events"
$ go get "github.com/aws/aws-lambda-go/lambda"
これでビルドが実施できるようになりました。
せっかくテンプレートにテストファイルが付いているのでテストしてみましょう。
// テストファイル
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/aws/aws-lambda-go/events"
)
func TestHandler(t *testing.T) {
t.Run("Unable to get IP", func(t *testing.T) {
DefaultHTTPGetAddress = "http://127.0.0.1:12345"
_, err := handler(events.APIGatewayProxyRequest{})
if err == nil {
t.Fatal("Error failed to trigger with an invalid request")
}
})
t.Run("Non 200 Response", func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
}))
defer ts.Close()
DefaultHTTPGetAddress = ts.URL
_, err := handler(events.APIGatewayProxyRequest{})
if err != nil && err.Error() != ErrNon200Response.Error() {
t.Fatalf("Error failed to trigger with an invalid HTTP response: %v", err)
}
})
t.Run("Unable decode IP", func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
}))
defer ts.Close()
DefaultHTTPGetAddress = ts.URL
_, err := handler(events.APIGatewayProxyRequest{})
if err == nil {
t.Fatal("Error failed to trigger with an invalid HTTP response")
}
})
t.Run("Successful Request", func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
fmt.Fprintf(w, "127.0.0.1")
}))
defer ts.Close()
DefaultHTTPGetAddress = ts.URL
_, err := handler(events.APIGatewayProxyRequest{})
if err != nil {
t.Fatal("Everything should be ok")
}
})
}
# テスト実施
$ cd go-api
go-api $ go test -v ./hello-world/
=== RUN TestHandler
=== RUN TestHandler/Unable_to_get_IP
=== RUN TestHandler/Non_200_Response
=== RUN TestHandler/Unable_decode_IP
=== RUN TestHandler/Successful_Request
--- PASS: TestHandler (0.00s)
--- PASS: TestHandler/Unable_to_get_IP (0.00s)
--- PASS: TestHandler/Non_200_Response (0.00s)
--- PASS: TestHandler/Unable_decode_IP (0.00s)
--- PASS: TestHandler/Successful_Request (0.00s)
PASS
ok _/Users/user/Documents/GitHub/api-demo/go-api/hello-world (cached)
問題ないですね。
ではビルドしてローカルでも実施しましょう。
# ビルド
$ make build
GOOS=linux GOARCH=amd64 go build -o hello-world/hello-world ./hello-world
# ローカル起動
$ sam local start-api
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2019-12-08 18:18:14 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
Invoking hello-world (go1.x)
これで準備できたのでリクエストを投げます。
# リクエスト
$ curl http://127.0.0.1:3000/hello
Hello, 10.100.110.111
# start-apiの画面
Fetching lambci/lambda:go1.x Docker container image......
Mounting /Users/user/Documents/GitHub/api-demo/go-api/hello-world as /var/task:ro,delegated inside runtime container
START RequestId: 0d0e4ea4-9c7c-1c63-b642-54048a3faadc Version: $LATEST
END RequestId: 0d0e4ea4-9c7c-1c63-b642-54048a3faadc
REPORT RequestId: 0d0e4ea4-9c7c-1c63-b642-54048a3faadc Init Duration: 159.66 ms Duration: 999.77 ms Billed Duration: 1000 ms Memory Size: 128 MB Max Memory Used: 26 MB
No Content-Type given. Defaulting to 'application/json'.
2019-12-08 18:20:31 127.0.0.1 - - [08/Dec/2019 18:20:31] "GET /hello HTTP/1.1" 200 -
リクエストが来たタイミングでDockerイメージがフェッチされるためレスポンスは少し遅いです。
ではデプロイしてみます。
$ sam deploy --guided
Configuring SAM deploy
======================
Configure
Looking for samconfig.toml : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]: go-api
AWS Region [us-east-1]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
Save arguments to samconfig.toml [Y/n]: Y
Looking for resources needed for deployment: Found!
Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-1gh4mza9gyjtv
A different default S3 bucket can be set in samconfig.toml
Saved arguments to config file
Running 'sam deploy' for future deployments will use the parameters saved above.
The above parameters can be changed by modifying samconfig.toml
Learn more about samconfig.toml syntax at
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
Deploying with following values
===============================
Stack name : go-api
Region : us-east-1
Confirm changeset : True
Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-1gh4mza9gyjtv
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Initiating deployment
=====================
Uploading to go-api/fff25b2d08e7c662a508f2b2277701bf 4342086 / 4342086.0 (100.00%)
Uploading to go-api/b3420e24fd8fe453f7bb26b67f59c286.template 1152 / 1152.0 (100.00%)
Waiting for changeset to be created..
CloudFormation stack changeset
---------------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType
---------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add HelloWorldFunctionCatchAllPermissionProd AWS::Lambda::Permission
+ Add HelloWorldFunctionRole AWS::IAM::Role
+ Add HelloWorldFunction AWS::Lambda::Function
+ Add ServerlessRestApiDeployment47fc2d5f9d AWS::ApiGateway::Deployment
+ Add ServerlessRestApiProdStage AWS::ApiGateway::Stage
+ Add ServerlessRestApi AWS::ApiGateway::RestApi
---------------------------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:us-east-1:123456789012:changeSet/samcli-deploy1575797125/4f1a8198-1d0a-4c1e-86d3-1a77fc7e587b
Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y
2019-12-08 18:25:44 - Waiting for stack create/update to complete
CloudFormation events from changeset
---------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus ResourceType LogicalResourceId ResourceStatusReason
---------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole Resource creation Initiated
CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole -
CREATE_COMPLETE AWS::IAM::Role HelloWorldFunctionRole -
CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction -
CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction Resource creation Initiated
CREATE_COMPLETE AWS::Lambda::Function HelloWorldFunction -
CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi Resource creation Initiated
CREATE_COMPLETE AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5f9 -
d
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5f9 Resource creation Initiated
d
CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionCatchAllPermission Resource creation Initiated
Prod
CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionCatchAllPermission -
Prod
CREATE_COMPLETE AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5f9 -
d
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_COMPLETE AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage Resource creation Initiated
CREATE_COMPLETE AWS::Lambda::Permission HelloWorldFunctionCatchAllPermission -
Prod
CREATE_COMPLETE AWS::CloudFormation::Stack go-api -
---------------------------------------------------------------------------------------------------------------------------------------------------------
Stack go-api outputs:
---------------------------------------------------------------------------------------------------------------------------------------------------------
OutputKey-Description OutputValue
---------------------------------------------------------------------------------------------------------------------------------------------------------
HelloWorldFunctionIamRole - Implicit IAM Role created for Hello World arn:aws:iam::123456789012:role/go-api-HelloWorldFunctionRole-19P29ZGX2R4XQ
function
HelloWorldAPI - API Gateway endpoint URL for Prod environment for First https://2v0ft8rx5a.execute-api.us-east-1.amazonaws.com/Prod/hello/
Function
HelloWorldFunction - First Lambda Function ARN arn:aws:lambda:us-east-1:123456789012:function:go-api-
HelloWorldFunction-1447XD3VZZWOP
---------------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - go-api in us-east-1
リクエスト確認してみます。
$ curl https://2v0ft8rx5a.execute-api.us-east-1.amazonaws.com/Prod/hello/
Hello, 10.100.110.111
問題ないですね。
ちなみに上記のターミナルで設定した項目はディレクトリ直下にsamconfig.toml
として保存されます。
version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "go-api"
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-1gh4mza9gyjtv"
s3_prefix = "go-api"
region = "us-east-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
S3バケットもできていますね。
$ aws s3 ls
2019-12-08 16:57:54 aws-sam-cli-managed-default-samclisourcebucket-1gh4mza9gyjtv
$ aws s3 ls aws-sam-cli-managed-default-samclisourcebucket-1gh4mza9gyjtv
PRE go-api/
チュートリアルはこれで完了です。
botの作成
今回はOutgoing webhookを利用して特定のチャンネルから特定のキーワードに対してレスポンスを返すようにします。
コードはこんな感じに変更しました。
package main
import (
"encoding/json"
"log"
"strings"
"net/url"
"errors"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
var (
ErrNon200Response = errors.New("Non 200 Response found")
)
type SlackMessage struct {
Text string `json:"text"`
}
func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
var keyword string
var ignore string
for _, value := range strings.Split(request.Body, "&") {
param := strings.Split(value, "=")
if param[0] == "trigger_word" {
keyword, _ = url.QueryUnescape(param[1])
}
if param[0] == "user_name" {
ignore, _ = url.QueryUnescape(param[1])
}
}
if ignore == "slackbot" {
return events.APIGatewayProxyResponse {}, nil
}
var text string
if keyword == "damn it" {
text = "What's happen bro!?"
} else if keyword == "weather" {
text = "The mood is always sunny!"
} else if keyword == "がんばる" || keyword == "頑張る" {
text = "おう!気張りやにーちゃん!"
} else if keyword == "つかれた" || keyword == "疲れた" {
text = "たまには休んでええんやで?"
} else if keyword == "おはよう" || keyword == "おはよー" {
text = "やかましいわ!もう少し寝させんかい!"
}
j, err := json.Marshal(SlackMessage{Text: text})
if err != nil {
log.Print(err)
return events.APIGatewayProxyResponse{Body: "Error"}, err
}
return events.APIGatewayProxyResponse{
Body: string(j),
StatusCode: 200,
}, nil
}
func main() {
lambda.Start(handler)
}
リクエストに対するレスポンスはfunc handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {}
内でラップしてあげる必要があるので注意です。
README_ApiGatewayEvent
デプロイ前準備
Makefile内でビルドした際のバイナリファイル名をしているものが、hello-world
のままだと気持ち悪いので変更しました。
build:
GOOS=linux GOARCH=amd64 go build -o hello-world/slack-bot ./hello-world
それに伴いtemplate.yaml
も関数名など変更しました。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
go-api
Slack Bot for test
Globals:
Function:
Timeout: 5
Resources:
slackBot:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: slack-bot
Runtime: go1.x
Tracing: Active
Events:
CatchAll:
Type: Api
Properties:
Path: /reply
Method: POST
Outputs:
slackBotAPI:
Description: "API Gateway endpoint URL for Prod environment for Slack Bot Function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/reply/"
slackBot:
Description: "Slack Bot Function"
Value: !GetAtt slackBot.Arn
slackBotIamRole:
Description: "Implicit IAM Role created for Slack Bot function"
Value: !GetAtt slackBotRole.Arn
sam packege
S3にファイルをアップロードする必要があるので実施します。
まずはS3バケットを作ります。
$ aws s3 mb s3://slack-bot-go --region us-east-1
make_bucket: slack-bot-go
下記コマンドを実施してLambdaのソースコードをパッケージングしてあげます。
$ sam package \
--output-template-file packaged.yaml \
--s3-bucket [BUCKET_NAME]
実行結果
$ sam package \
> --output-template-file packaged.yaml \
> --s3-bucket slack-bot-go
Uploading to 5626ff5aa050e5296410bbde19d2bd56 8654225 / 8654225.0 (100.00%)
Successfully packaged artifacts and wrote output template to file packaged.yaml.
Execute the following command to deploy the packaged template
sam deploy --template-file /Users/user/Documents/GitHub/api-demo/go-api/packaged.yaml --stack-name <YOUR STACK NAME>
$ aws s3 ls s3://slack-bot-go
2019-12-08 21:43:03 8654225 5626ff5aa050e5296410bbde19d2bd56
無事にアップロードできています。
sam deploy
ここで別のStackとして管理したい場合は再度--guided
オプションをつけてあげればいいのですが、今回はhello-world
周りのリソースが不要なのでそのままsamconfig.toml
を利用してついでにいらないものも消してしまいます。
※ちなみに初回はsam deploy
だけだと怒られます。
$ sam deploy
Usage: sam deploy [OPTIONS]
Try "sam deploy --help" for help.
Error: Missing option '--stack-name', 'sam deploy --guided' can be used to provide and save needed parameters for future deploys.
ではデプロイしてみます。
$ sam deploy
# samconfig.tomlが読み込まれているのがわかる
Deploying with following values
===============================
Stack name : go-api
Region : us-east-1
Confirm changeset : True
Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-1gh4mza9gyjtv
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Initiating deployment
=====================
Uploading to go-api/5626ff5aa050e5296410bbde19d2bd56 8654225 / 8654225.0 (100.00%)
Uploading to go-api/a4559abc92e44da84a60149a1a9f09a5.template 1020 / 1020.0 (100.00%)
Waiting for changeset to be created..
CloudFormation stack changeset
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add ServerlessRestApiDeploymentcdebb0ffc6 AWS::ApiGateway::Deployment
+ Add slackBotCatchAllPermissionProd AWS::Lambda::Permission
+ Add slackBotRole AWS::IAM::Role
+ Add slackBot AWS::Lambda::Function
* Modify ServerlessRestApiProdStage AWS::ApiGateway::Stage
* Modify ServerlessRestApi AWS::ApiGateway::RestApi
# 不要なリソースがチェンジセットから削除されることを確認できる
- Delete HelloWorldFunctionCatchAllPermissionProd AWS::Lambda::Permission
- Delete HelloWorldFunctionRole AWS::IAM::Role
- Delete HelloWorldFunction AWS::Lambda::Function
- Delete ServerlessRestApiDeployment47fc2d5f9d AWS::ApiGateway::Deployment
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:us-east-1:123456789012:changeSet/samcli-deploy1575809093/6a54bb0c-1a8f-4659-b32d-198226d652cb
Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y
2019-12-08 21:45:17 - Waiting for stack create/update to complete
CloudFormation events from changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus ResourceType LogicalResourceId ResourceStatusReason
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS AWS::IAM::Role slackBotRole Resource creation Initiated
CREATE_IN_PROGRESS AWS::IAM::Role slackBotRole -
CREATE_COMPLETE AWS::IAM::Role slackBotRole -
CREATE_IN_PROGRESS AWS::Lambda::Function slackBot -
CREATE_IN_PROGRESS AWS::Lambda::Function slackBot Resource creation Initiated
CREATE_COMPLETE AWS::Lambda::Function slackBot -
UPDATE_COMPLETE AWS::ApiGateway::RestApi ServerlessRestApi -
UPDATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeploymentcdebb0ffc6 -
CREATE_COMPLETE AWS::ApiGateway::Deployment ServerlessRestApiDeploymentcdebb0ffc6 -
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeploymentcdebb0ffc6 Resource creation Initiated
CREATE_IN_PROGRESS AWS::Lambda::Permission slackBotCatchAllPermissionProd Resource creation Initiated
CREATE_IN_PROGRESS AWS::Lambda::Permission slackBotCatchAllPermissionProd -
UPDATE_COMPLETE AWS::ApiGateway::Stage ServerlessRestApiProdStage -
UPDATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_COMPLETE AWS::Lambda::Permission slackBotCatchAllPermissionProd -
UPDATE_COMPLETE_CLEANUP_IN_PROGRESS AWS::CloudFormation::Stack go-api -
DELETE_COMPLETE AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5f9d -
DELETE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionCatchAllPermissionProd -
DELETE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5f9d -
DELETE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction -
DELETE_COMPLETE AWS::Lambda::Permission HelloWorldFunctionCatchAllPermissionProd -
DELETE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole -
DELETE_COMPLETE AWS::Lambda::Function HelloWorldFunction -
UPDATE_COMPLETE AWS::CloudFormation::Stack go-api -
DELETE_COMPLETE AWS::IAM::Role HelloWorldFunctionRole -
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Stack go-api outputs:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
OutputKey-Description OutputValue
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
slackBotAPI - API Gateway endpoint URL for Prod environment for Slack Bot Function https://2v0ft8rx5a.execute-api.us-east-1.amazonaws.com/Prod/reply/
slackBot - Slack Bot Function arn:aws:lambda:us-east-1:123456789012:function:go-api-slackBot-8OP317NRCY48
slackBotIamRole - Implicit IAM Role created for Slack Bot function arn:aws:iam::123456789012:role/go-api-slackBotRole-T7GY3L3LQGK5
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - go-api in us-east-1
デプロイ完了しました。
リポジトリ
Outgoing Webhookの設定
Outgoing Webhook側で作成したリソースに対してアクションするように設定してあげます。
今回は引き金となる言葉
/ URL
/ チャンネル
/ 名前
/ アイコン
を設定しました。
これでOKです。
テスト
では早速設定したキーワードを投げてみます。
ちゃんと返ってきましたー。
削除
こんなbotは使い所なくて邪魔なだけなので消しましょうw
公式ドキュメントにdestroy
とかterminate
みたいなコマンドはなさそうですね。
CloudFormationから消してあげるしかなさそうです。
S3空にしないといけないので、コマンド一発で消せないのはちょっと辛いですね。。
$ aws cloudformation delete-stack --stack-name go-api
まとめ
AWS SAM
自体は正直すこし冗長だなあ…というのが個人的な感想でした。
その前にAWS CDK
を触っていた反動もあるとは思いますが、、
うまく組み合わせてAWS SAM
にはLambda関数の部分におけるテストやローカル実行、AWS CDK
には周辺リソースなどを作ってもらう形がいいのではないかと思いました。
両方とも活用することでより便利に、安全なサーバーレス開発が捗ると思います。