[AWS] Serverless Application Model (SAM) でAPI Gateway + Lambda + DynamoDBなサンプルを作成してみる

# 前提
## 環境
今回は、Macの環境で行いますが、セットアップ方法以外の実行に際しては、Windows / Linuxでも大きな差異はないので、ご参考になるかと思います。

## 構成

- API Gateway
- Lambda (Python 3.7)
- DynamoDB

boto3によりDynamoDBにレコードを作成するサンプルを作成してみたいと思います。

## その他必要なもの

- AWSアカウント

# セットアップ
まずは、「[[AWS] Serverless Application Model (SAM) の基本まとめ](https://qiita.com/herohit-tool/items/420f4a7b294cfcf56ed7)」にあわせてセットアップをしていきたいと思います。

- Docker Desktopをインストールする
- Dockerを起動する

```bash:Version確認
$ docker -v
Docker version 19.03.12, build 48a66213fe
```
- Homebrewをインストールする

```bash:Version確認
$ brew -v
Homebrew 2.4.8
Homebrew/homebrew-core (git revision 820df; last commit 2020-07-27)
Homebrew/homebrew-cask (git revision 35ad13; last commit 2020-07-28)
```

- SAM CLIをインストールする

```bash:Install-tap
$ brew tap aws/tap
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/cask).
No changes to formulae.

==> Tapping aws/tap
Cloning into '/usr/local/Homebrew/Library/Taps/aws/homebrew-tap'...
remote: Enumerating objects: 20, done.
remote: Counting objects: 100% (20/20), done.
remote: Compressing objects: 100% (16/16), done.
remote: Total 654 (delta 8), reused 10 (delta 4), pack-reused 634
Receiving objects: 100% (654/654), 134.26 KiB | 384.00 KiB/s, done.
Resolving deltas: 100% (297/297), done.
Tapped 5 formulae (42 files, 208.2KB).
```

```bash:Install-sam-cli
$ brew install aws-sam-cli
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
==> Updated Formulae
pnpm                                     xxhash

==> Installing aws-sam-cli from aws/tap
==> Downloading https://github.com/awslabs/aws-sam-cli/releases/download/v1.0.0/
Already downloaded: /Users/******/Library/Caches/Homebrew/downloads/e6dd54932e5f527780cb97f2a3637d334e4e40bf26c83783ab6b9e7740cd568e--aws-sam-cli-1.0.0.sierra.bottle.tar.gz
==> Pouring aws-sam-cli-1.0.0.sierra.bottle.tar.gz
🍺  /usr/local/Cellar/aws-sam-cli/1.0.0: 4,109 files, 83MB
```

Python 3.8xがインストール済みの場合、失敗することがあります。

```bash:Version確認
$ sam --version
SAM CLI, version 1.0.0
```

あと、AWS CLIも必要になるため、インストールされていない場合は、

```bash:install-aws-cli
$ brew install awscli
```

でインストールしてください。

```bash:Version確認
$ aws --version
aws-cli/2.0.34 Python/3.8.5 Darwin/19.6.0
```

また、今回はboto3を使用するので、

```bash:install-boto3
$ pip install boto3
```

でboto3のインストールもお願いします。

# SAMでアプリケーション作成
## まずは HelloWorld から
### プロジェクトの作成
まずは、作業用のディレクトリを用意します。

```bash
$ mkdir samapp
$ cd samapp
```

Pythonの環境も仮想環境にしておきましょうか。

```bash
$ python -m venv venv
$ . ./venv/bin/activate
```

では、さっそく雛形を作成していきましょう。

```bash:init
$ sam init --runtime python3.7
Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 1

Project name [sam-app]:

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
	1 - Hello World Example
	2 - EventBridge Hello World
	3 - EventBridge App from scratch (100+ Event Schemas)
	4 - Step Functions Sample App (Stock Trader)
	5 - Elastic File System Sample App
Template selection: 1

-----------------------
Generating application:
-----------------------
Name: sam-app
Runtime: python3.7
Dependency Manager: pip
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./sam-app/README.md
```

途中で入力が必要な箇所がありますが、まずはオーソドックスに、

- Template source: AWS Quick Start Templates
- Project Name : sam-app
- Template : Hello World Example

としておきます。

### ビルド
`sam init`で、プロジェクト名のディレクトリが作成されます。今回はデフォルトのまま`sam-app`を指定しましたので、プロジェクトディレクトリに移動してから、ビルドを実行します。

```build
$ cd sam-app/
$ sam build
Building function 'HelloWorldFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
```

### 実行
ローカルで動作させるためには、Dockerが動作している必要があります。
もし、Dockerのエンジンが起動されていない場合は、起動しておいてください。

```bash:start-api
$ 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
2020-07-28 11:54:52  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
```

これで起動している状態なので、ブラウザで`http://127.0.0.1:3000/hello`にアクセスしてみましょう。
初回は、準備等が実行されるので、少し時間がかかりますが、少し待つと、ブラウザに

```json
{"message": "hello world"}
```

と表示されるかと思います。これで、ローカル環境のDocker上の仮装的なLambda環境が動作していることが確認できたと思います。
この状態で、コンソールで「Ctrl+C」でdockerコンテナが停止しましょう。

## リファクタリング
### コードディレクトリ名の変更
では、現在「HelloWorld」用に構成されているアプリケーションを少しいじってみましょう。
まずは。プロジェクト配下にあるディレクトリ名を変更したいと思います。
現在「hello_world」というディレクトリができているので、これを「sam_ddb」に変更してみることにしましょう。

```bash
$ mv hello_world sam_ddb
```

### template.ymlの変更
SAMの定義でもあるテンプレートを修正します。
ここでは、「hello_world」

```yml:template.yml変更前
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn
```

```yml:template.yml変更後
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 30

Resources:
  SamDdbFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: sam_ddb/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        SamDdb:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /ddb
            Method: get

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  SamDdbApi:
    Description: "API Gateway endpoint URL for Prod stage for SAM DDB function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/ddb/"
  SamDdbFunction:
    Description: "SAM DDB Lambda Function ARN"
    Value: !GetAtt SamDdbFunction.Arn
  SamDdbFunctionIamRole:
    Description: "Implicit IAM Role created for SAM DDB function"
    Value: !GetAtt SamDdbFunctionRole.Arn
```

### テストコード
今回は使用しませんが、ついでにテスト用のコードも修正してしまいましょう。
5行目を以下のように修正します。

```python:tests/unit/test_handler.py修正前
from hello_world import app
```

```python:tests/unit/test_handler.py修正後
from sam_ddb import app
```

### ビルド
この段階で、一度ビルドしてみましょう。

```bash:build
$ sam build
Building function 'SamDdbFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
```

Building functionが変更されていることが確認できると思います。

### 実行
では、先ほど同様、ローカルで実行してみましょう。

```bash:start-api
$ sam local start-api
Mounting SamDdbFunction at http://127.0.0.1:3000/ddb [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
2020-07-28 13:29:59  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
```

こちらも成功し、URLが`http://127.0.0.1:3000/ddb`に変わっていることが確認できるかと思います。
そして、`http://127.0.0.1:3000/ddb`にブラウザでアクセスして、先ほどと同じ結果になることを確認しましょう。

## ローカルにDynamoDBの環境を構築してみる
次は、DynamoDBへのアクセスですが、AWS上にプロビジョニングする前に、ローカルでLambda関数のテストを行うために、ローカルにDynamoDBの環境を作る必要があります。

### テーブル定義
まずは、テーブルの構造をJsonファイルで定義します。
下記内容を格納するディレクトリとして、プロジェクトのディレクトリ直下に「ddb」というディレクトリを作成してください。
その中に「ddb.json」というファイル名で、下記内容のファイルを作成してみましょう。

```json:ddb.json
{
  "TableName": "Demo",
  "KeySchema": [
    {
      "AttributeName": "Key",
      "KeyType": "HASH"
    },
    {
      "AttributeName": "CreateDate",
      "KeyType": "RANGE"
    }
  ],
  "AttributeDefinitions": [
    {
      "AttributeName": "Key",
      "AttributeType": "S"
    },
    {
      "AttributeName": "CreateDate",
      "AttributeType": "S"
    }
  ],
  "ProvisionedThroughput": {
    "ReadCapacityUnits": 5,
    "WriteCapacityUnits": 5
  }
}
```

### Lambdaの修正
では、現在「hello world」というJsonデータを返すだけのLambdaで、DynamoDBにレコードを登録するような実装に変更してみましょう。
今回は、POSTリクエストに渡された「key」に指定された内容をDynamoDBに登録するサンプルにしたいと思います。
CreateDateは、現在日時を取得して格納します。
以下が、書き換え後のサンプルコードです。

```python:app.py
import json
import boto3
import os
from datetime import datetime

def lambda_handler(event, context):
    try:
        event_body = json.loads(event["body"])
        if "local" in event_body and event_body["local"] == True:
            dynamodb = boto3.resource("dynamodb", endpoint_url="http://dynamodb:8000")
        else:
            dynamodb = boto3.resource("dynamodb")

        table = dynamodb.Table("Demo")
        table.put_item(
            Item={
                "Key": event_body["key"],
                "CreateDate": datetime.utcnow().isoformat()
            }
        )

        return {
            "statusCode": 200,
            "body": json.dumps({
                "message": "succeeded",
            }),
        }
    except Exception as e:
        return {
            "statusCode": 500,
            "body": json.dumps({
                "message": e.args
            }),
    }
```

### 定義の修正
まず、Pythonでboto3を使用するため`requirements.txt`に「boto3」を追記し、以下のような内容に修正します。

```text:requirements.txt
requests
boto3
```

そして、SAMのテンプレートも、少しだけ修正を加えます。

25行目

```yaml:template.yml変更前
            Method: get
```

```yaml:template.yml変更後
            Method: post
```

### DynamoDBを起動する
ローカルでのDynamoDBはDockerコンテナで動作させることができます。
以下での手順でDockerコンテナを起動するところまで実行してみましょう。

```bash
$ docker pull amazon/dynamodb-local
Using default tag: latest
latest: Pulling from amazon/dynamodb-local
638b75f800bf: Pull complete
a455ee0c5c58: Pull complete
c7e215ba3460: Pull complete
Digest: sha256:c8aeb014edfff8b93d7f14054cb9d2a1be214d62b7c5b61eb7fb40893a8404bb
Status: Downloaded newer image for amazon/dynamodb-local:latest
docker.io/amazon/dynamodb-local:latest
```
```bash
$ docker network create ddb-network
5c81f9e8f32413cfcf9aec4d4165d6f53cee67d167ad87d2e99813b90219c422
```

```bash
$ docker run --network ddb-network --name dynamodb -p 8000:8000 amazon/dynamodb-local -jar DynamoDBLocal.jar -sharedDb
Initializing DynamoDB Local with the following configuration:
Port:	8000
InMemory:	false
DbPath:	null
SharedDb:	true
shouldDelayTransientStatuses:	false
CorsParams:	*
```

この状態で一度「Ctrl+C」でコンテナを停止し、

```bash
$ docker start dynamodb
```

を実行してください。バックグランドでDynamoDBが起動されます。

### ローカルDynamoDBにテーブル定義を追加する
現在起動しているローカルのDynamoDBにはテーブルの定義がありませんので、先ほど「ddb/ddb.json」で定義した内容でテーブルを作成したいと思います。

```bash
$ aws dynamodb create-table --cli-input-json file://./ddb/ddb.json --endpoint-url http://localhost:8000
```
これで、ローカルのDynamoDBに、テーブルが作成されました。

### ビルド
では、例によって、ビルドを実行したいと思います。

```bash:build
$ sam build
Building function 'SamDdbFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
```

### 実行

では、さっそく実行してみます。
今回は、ローカルのDynamoDBのコンテナと通信するために、`--docker-netork`オプションが必要になるので注意してください。

```bash:start-api
$ sam local start-api --docker-network ddb-network
Mounting SamDdbFunction at http://127.0.0.1:3000/ddb [POST]
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
2020-07-28 15:20:35  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
```

起動できましたね。
今度は、前回と違って「POST」でアクセスする必要があります。
したがって、今回はブラウザではなく、`curl`でアクセスしてみたいと思います。

```bash
$ curl -X POST -H "Content-Type: application/json" -d '{"key": "demo-data", "local": true}' http://127.0.0.1:3000/ddb
```

この実行の結果

```json
{"message": "succeeded"}
```

が表示されると、成功です。

### DB確認

では、実際にデータがDynamoDBに登録できたかを確認してみましょう。

```bash
$ aws dynamodb scan --table-name Demo --endpoint-url http://localhost:8000
```

```json
{
    "Items": [
        {
            "CreateDate": {
                "S": "2020-07-28T06:23:33.150156"
            },
            "Key": {
                "S": "demo-data"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}
```

これでLambda関数でのDynamoDBへのデータ登録ができることがローカルで確認することができました。

### 後始末
Lambda環境は「Ctrl+C」で終了できます。
あと、DynamoDBについては、以下のコマンドで終了させてください。

```bash
$ docker stop dynamodb
$ docker rm dynamodb
```

## AWSへのデプロイ
### SAMテンプレート修正
ここまできたら、デプロイのための定義をテンプレートに記述してデプロイするだけです。
テンプレートの修正は

- Resourceの中にDynamoDBのテーブルの定義追加
- Lambda関数のIAMロールの定義追加
- Lambda関数へのIAMロール適用

が主な変更点です。

```yml:template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

Globals:
  Function:
    Timeout: 30

Resources:
  DynamoTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: Demo
      AttributeDefinitions:
        - AttributeName: Key
          AttributeType: S
        - AttributeName: CreateDate
          AttributeType: S
      KeySchema:
        - AttributeName: Key
          KeyType: HASH
        - AttributeName: CreateDate
          KeyType: RANGE
      ProvisionedThroughput: 
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5
  
  SamDdbFunction:
    Type: AWS::Serverless::Function
    Properties:
      Role: !GetAtt SamDdbFunctionIamRole.Arn
      CodeUri: sam_ddb/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        SamDdb:
          Type: Api
          Properties:
            Path: /ddb
            Method: post

  SamDdbFunctionIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - 'lambda.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/CloudWatchLogsFullAccess'
      Policies:
        - PolicyName: 'SamDdbPolicy'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:PutItem
                Resource: !GetAtt DynamoTable.Arn

Outputs:
  SamDdbApi:
    Description: "API Gateway endpoint URL for Prod stage for SAM DDB function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/ddb/"
```

### AWSクレデンシャルの設定
AWSにDeployするために、クレデンシャルを設定します。
`aws config`で設定してもよいですが、今回は、環境変数に設定するやり方にしたいと思います。
環境変数に設定する内容は、マネジメントコンソールのIAMより、認証情報からアクセスキーを生成します。
[アクセスキーの管理（コンソール）](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey)

```bash
$ export AWS_ACCESS_KEY_ID="アクセスキーID"
$ export AWS_SECRET_ACCESS_KEY="シークレットアクセスキー"
```

で環境変数をコンソールに設定します。
ここまで準備ができたら、あとはビルドしてデプロイを実行するだけです。

```bash:build
$ sam build
Building function 'SamDdbFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
```

続いてデプロイですが、今回は対話形式で進めたいと思います。

```bash:deploy
$ sam deploy --guided

Configuring SAM deploy
======================

	Looking for samconfig.toml :  Found
	Reading default arguments  :  Success

	Setting default arguments for 'sam deploy'
	=========================================
	Stack Name [sam-app]:
	AWS Region [ap-northeast-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
	SamDdbFunction may not have authorization defined, Is this okay? [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-pqa3p7mrbwhc
		A different default S3 bucket can be set in samconfig.toml

	Deploying with following values
	===============================
	Stack name                 : sam-app
	Region                     : ap-northeast-1
	Confirm changeset          : True
	Deployment s3 bucket       : aws-sam-cli-managed-default-samclisourcebucket-pqa3p7mrbwhc
	Capabilities               : ["CAPABILITY_IAM"]
	Parameter overrides        : {}

Initiating deployment
=====================

	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                 : sam-app
	Region                     : ap-northeast-1
	Confirm changeset          : True
	Deployment s3 bucket       : aws-sam-cli-managed-default-samclisourcebucket-pqa3p7mrbwhc
	Capabilities               : ["CAPABILITY_IAM"]
	Parameter overrides        : {}

Initiating deployment
=====================
SamDdbFunction may not have authorization defined.
Uploading to sam-app/c72e0b0797684ec987c82c67edd96573.template  1949 / 1949.0  (100.00%)

Waiting for changeset to be created..

CloudFormation stack changeset
------------------------------------------------------------------------------------------------
Operation                        LogicalResourceId                ResourceType
------------------------------------------------------------------------------------------------
+ Add                            DynamoTable                      AWS::DynamoDB::Table
+ Add                            SamDdbFunctionIamRole            AWS::IAM::Role
+ Add                            SamDdbFunctionSamDdbPermission   AWS::Lambda::Permission
                                 Prod
+ Add                            SamDdbFunction                   AWS::Lambda::Function
+ Add                            ServerlessRestApiDeploymentd34   AWS::ApiGateway::Deployment
                                 ff828b5
+ Add                            ServerlessRestApiProdStage       AWS::ApiGateway::Stage
+ Add                            ServerlessRestApi                AWS::ApiGateway::RestApi
------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:703609122166:changeSet/samcli-deploy1595922824/c5c5c271-c3f7-4ffe-916f-d02a10b034d4


Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y

2020-07-28 16:53:59 - Waiting for stack create/update to complete

CloudFormation events from changeset
-------------------------------------------------------------------------------------------------
ResourceStatus           ResourceType             LogicalResourceId        ResourceStatusReason
-------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS       AWS::DynamoDB::Table     DynamoTable              Resource creation
                                                                           Initiated
CREATE_IN_PROGRESS       AWS::DynamoDB::Table     DynamoTable              -
CREATE_COMPLETE          AWS::DynamoDB::Table     DynamoTable              -
CREATE_IN_PROGRESS       AWS::IAM::Role           SamDdbFunctionIamRole    -
CREATE_IN_PROGRESS       AWS::IAM::Role           SamDdbFunctionIamRole    Resource creation
                                                                           Initiated
CREATE_COMPLETE          AWS::IAM::Role           SamDdbFunctionIamRole    -
CREATE_IN_PROGRESS       AWS::Lambda::Function    SamDdbFunction           -
CREATE_IN_PROGRESS       AWS::Lambda::Function    SamDdbFunction           Resource creation
                                                                           Initiated
CREATE_COMPLETE          AWS::Lambda::Function    SamDdbFunction           -
CREATE_IN_PROGRESS       AWS::ApiGateway::RestA   ServerlessRestApi        Resource creation
                         pi                                                Initiated
CREATE_IN_PROGRESS       AWS::ApiGateway::RestA   ServerlessRestApi        -
                         pi
CREATE_COMPLETE          AWS::ApiGateway::RestA   ServerlessRestApi        -
                         pi
CREATE_IN_PROGRESS       AWS::Lambda::Permissio   SamDdbFunctionSamDdbPe   -
                         n                        rmissionProd
CREATE_IN_PROGRESS       AWS::ApiGateway::Deplo   ServerlessRestApiDeplo   -
                         yment                    ymentd34ff828b5
CREATE_IN_PROGRESS       AWS::ApiGateway::Deplo   ServerlessRestApiDeplo   Resource creation
                         yment                    ymentd34ff828b5          Initiated
CREATE_IN_PROGRESS       AWS::Lambda::Permissio   SamDdbFunctionSamDdbPe   Resource creation
                         n                        rmissionProd             Initiated
CREATE_COMPLETE          AWS::ApiGateway::Deplo   ServerlessRestApiDeplo   -
                         yment                    ymentd34ff828b5
CREATE_IN_PROGRESS       AWS::ApiGateway::Stage   ServerlessRestApiProdS   -
                                                  tage
CREATE_IN_PROGRESS       AWS::ApiGateway::Stage   ServerlessRestApiProdS   Resource creation
                                                  tage                     Initiated
CREATE_COMPLETE          AWS::ApiGateway::Stage   ServerlessRestApiProdS   -
                                                  tage
CREATE_COMPLETE          AWS::Lambda::Permissio   SamDdbFunctionSamDdbPe   -
                         n                        rmissionProd
CREATE_COMPLETE          AWS::CloudFormation::S   sam-app                  -
                         tack
-------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
-------------------------------------------------------------------------------------------------
Outputs
-------------------------------------------------------------------------------------------------
Key                 SamDdbApi
Description         API Gateway endpoint URL for Prod stage for SAM DDB function
Value               https://l6uressyl8.execute-api.ap-northeast-1.amazonaws.com/Prod/ddb/
-------------------------------------------------------------------------------------------------

Successfully created/updated stack - sam-app in ap-northeast-1
```

AWS Regionは、東京リージョンを指定するようにしてください。それ以外の「y/n」の質問はすべて「y」で問題ありません。
また、CloudFormationのスタック名も、他と重複しないような名前であれば特になんでもよいので、今回はデフォルトのアプリケーション名のままとしました。

### 実行
`sam deploy`の最後に、API GatewayのURLが表示されているので、このURLに対してリクエストを発行するようにします。

```bash
$ curl -X POST -H "Content-Type: application/json" -d '{"key": "demo-data"}' https://l6uressyl8.execute-api.ap-northeast-1.amazonaws.com/Prod/ddb/
```

はい、成功のメッセージが返ってきました。

```json
{"message": "succeeded"}
```

では、マネジメントコンソールで、DynamoDBの中身をみてみましょう。

![DynamoDB_·_AWS_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427998/6417e514-7be3-95b1-a2a8-15440947c258.png)

Demoテーブルに、きちんとデータが登録できていることが確認できました。

# まとめ
今回は手順を踏んで、HelloWorldのテンプレートから少しずつアプリケーションの形に変えていく形で作成してみましたが、慣れてくれば直接テンプレートを作成する方法でもできないことはないと思います。
あと、今回はテストコードについては放置しましたが、できればテストコードもきちんと対応しましょう。
SAMのテンプレートでは、複数のLambda関数を定義することもできますし、DynamoDB以外のリソース定義を行うこともできるので、Serverlessなアプリケーションを作る際には、十分実用価値があるのでは、と思います。

あと、SAMではなくCDKで同様のことを実現する「[[AWS] CDKで、API Gateway + Lambda + DynamoDBなサンプルを作成してみる](https://qiita.com/herohit-tool/items/076b2971e7a7faf1f1ca)」も投稿しましたので、こtいらもよろしくです。

# サンプルコードリポジトリ

https://github.com/hito-psv/sam-demo-001