AWS SAM CLIでWebアプリをサクッと作ってサーバーレスアプリケーションに親しむ。

AWS Serverless Application Model（AWS SAM）
数分でサーバレスアプリケーションに必要なリソースを揃えてデプロイ＆ローカル開発環境も提供してくれるます。
AWSコンソールに慣れていなくても、とりあえず動くものが立ち上がって、いろいろいじれるところから始められるので非常に楽しいです。

# 記事の対象

- Webアプリをつくる人
- AWSをすこしいじったことあるけど面倒そうだなとか思って後回しにしていた人
- AWSでサーバレスウェブアプリを作りたい人

# 準備とインストール

一度もAWSいじったこと無い場合は準備だけ必要です。

## AWS CLIをインストール
AWSをターミナルからコマンドでいろいろいじれるようになります。
インストールして認証するまではちょっと頑張らないとかも。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-welcome.html

## AWS SAM CLIをインストール
今回インストールしたいもの。下記から手順にしたがってインストール（Dockerなどが必要です）
https://aws.amazon.com/jp/serverless/sam/

# 手順

## とりあえず開始

必要なもののインストールさえ済めばあとはかんたんです。
任意のディレクトリでコマンド

```
$ sam init
```

いろいろ聞いてくれるので答えるだけで必要なコードが出来上がります。

```
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1 <-- 最初なので準備してあるテンプレートを選んでサクッと作るモードを選択

Which runtime would you like to use?
        1 - nodejs12.x
        ・・・・
Runtime: 1 <-- 得意な言語を選ぶだけ。

Project name [sam-app]: <-- お好きな名前を。デフォルトはsam-appなのでそのままでもOK

AWS quick start application templates:
        ・・・・
        8 - Quick Start: Web Backend
Template selection: 8 <-- WebアプリのCRUD一式のサンプルテンプレートが出来上がります。
```

これでしばらく待つと、アプリの出来上がりです。
Lambda＋DynamoDB＋API Gatewayの、よくあるサーバレスWebアプリセットによるデータの登録・取得ができるようになっています。

早速AWSで動かします。（何もいじらなくても動かせるのです！）

```
$ cd sam-app <-- 先程指定したProject名がディレクトリ名になるので移動
$ sam build <-- ビルド
```

で、ビルド完了。必要なモジュールなども入って実行可能な状態になります。続いてデプロイ。

```
$ sam deploy --guided
```

色々聞いてくれるので

```
Stack Name [sam-app]: <-- 任意の名前を。そのままでもOK
AWS Region [us-east-1]: ap-northeast-1 <-- リージョン名（日本だとnortheast?）
....
あとは大体Yで行けるはず。
```
で、出来上がりです。コードどころか、AWSのコンパネでなにかクリックとかなくてもかんたんなウェブアプリが出来上がりました。

# 試す

まずは、出来上がったアプリのURIを見つける。

```
ーーーー
Key                 WebEndpoint                                                                 
Description         API Gateway endpoint URL for Prod stage                                     
Value               https://****.execute-api.[リージョン名].amazonaws.com/Prod/
```
みたいなところを探す。ValueのところのがアプリのエンドポイントURIです。（以下　`http://***/Prod/` とします。）

## 実行

まずは、データのPOST

```
$ curl -X POST \
-H "Content-Type: application/json" \
-d '{"id":"a1", "name":"Sample Taro"}' \
http://***/Prod/
```

id: a1
name: Sample Taro
という値を登録しました。

次にデータの取得

```
$ curl http://***/Prod/ai
```
先程登録したデータが見れるはず。

また、

```
$ curl http://***/Prod/
```
で全件表示。

以上で、あっというまにデータの登録・取得できる環境がサーバレスで動くようになりました。
とても便利で楽しい。

# デプロイしたデータの削除

デプロイしたままだと、お金もかかるので、一旦AWSから削除します。

```
$ aws cloudformation delete-stack --stack-name aws-sam-getting-started --region リージョン名
```

上記でエラーとかになったら
AWSコンソールから、CloudFormationを開いて手動で削除します。（自分の環境の場合権限が足りなかったみたいで、削除できなかったので手動でやりました。）


# ローカルで開発環境

ここまでやったら、せっかくなので、いろいろいじってみたいところです。
動くものをいろいろいじるのが理解も早いし楽しく学べます。

## DynamoDB Localのインストール

まずは、ローカルのdocker環境でDynamoDBサーバを動かせるコンテナを立ち上げます。

```
$ docker pull amazon/dynamodb-local
```

dockerを立ち上げますが、ここでちょっと一工夫必要でした。
SAMのLambdaもローカルのdockerコンテナ上で実行されるため、DynamoDB Localと通信できるようにしておかないといけません。

検索するといろいろな方法が紹介されていますが、
実験的に何度も立てたり消したりできるように、docker-compose.ymlに記述しておく方法が便利でした。
[こちら](https://selmertsx.hatenablog.com/entry/2019/04/25/SAM_Local%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6Local%E3%81%A7%E5%8B%95%E3%81%8B%E3%81%97%E3%81%A6%E3%81%84%E3%82%8BAWS_Lambda_%E3%81%8B%E3%82%89dynamodb-local%E3%81%AB%E3%82%A2%E3%82%AF)のをほぼそのまま使わせていただきます。

```yaml:docker-compose.yml
version: "3"

services:
  dynamodb-local:
    container_name: dynamodb
    image: amazon/dynamodb-local
    build: ./
    ports:
      - 8000:8000
    command: -jar DynamoDBLocal.jar -dbPath /data -sharedDb
    volumes:
      - ./dynamodb-data:/data
    networks:
      - lambda-local
networks:
  lambda-local:
    external: true
```

ポイントは、docker-networksで、`lambda-local`を設定しています。（後で使います）

DynamoDB Localを起動

```
$ docker-compose up -d
```

### 確認
```
$ aws dynamodb list-tables --endpoint-url http://localhost:8000

{
    "TableNames": []
}
```
みたいに表示されたらOKです。

### DynamoDB Localにテーブル作成

ローカルで使うのでテーブルを一つ作成しておきます。
以下のファイルでテーブルを定義

```json:LocalSampleTable.json
{
    "TableName": "SampleTable",
    "KeySchema": [
        {
            "AttributeName": "id",
            "KeyType": "HASH"
        }
    ],
    "AttributeDefinitions": [
        {
            "AttributeName": "id",
            "AttributeType": "S"
        }
    ],
    "ProvisionedThroughput": {
        "ReadCapacityUnits": 1,
        "WriteCapacityUnits": 1
    }

}
```
テーブル作成のコマンド

```
$ aws dynamodb create-table --endpoint-url http://localhost:8000 --cli-input-json file://LocalSampleTable.json
```
上で作ったLocalSampleTable.jsonからテーブルを作成しました。


## Lambdaのコード修正

今度は、ローカルのLambdaからDynamoDB Localに接続するようにコードを修正
このプロジェクト環境では3つの実行用JSファイルがありますが、どのファイルも修正箇所は一緒です。

```javascript:src/handlers/get-all-items.js
...

const tableName = process.env.SAMPLE_TABLE;

const dynamodb = require('aws-sdk/clients/dynamodb');
const docClient = new dynamodb.DocumentClient();

...
```
ここがDBへの接続なので、以下のように修正

```javascript:src/handlers/get-all-items.js

const tableName = "SampleTable"; // 先程ローカルに作成したテーブル名

const dynamodb = require('aws-sdk/clients/dynamodb');

// DynamoDB Localのエンドポイントを指定して接続
const dynamoOpt = {
    endpoint: "http://dynamodb:8000"
};
const docClient = new dynamodb.DocumentClient();

```

## ビルドして起動

```
$ sam build

$ sam local start-api --docker-network lambda-local
```

`sam local start-api` はローカルでテストするためのAPIを起動します。
`--docmer-network` でDockerのネットワークを指定することができるので、DynamoDBを起ち上げるときの `docker-compose.yml` で設定したネットワークに接続することができます。
これにより、Lambda側から、`endpoint: "http://dynamodb:8000"` でDynamoDB Localのコンテナに接続できるようになります。
ということを、いろいろなサイトを参考にして理解しました。ここが一番苦労しました。

## ローカルで確認

ローカルのAPIを起動するとURIを表示してくれるので（おそらく、`http://127.0.0.1:3000/` ）、アクセスしてみる。

```
$ curl http://127.0.0.1:3000/
```

テーブルの内容を取得できたらOK
おなじく、他のJSファイルも修正すれば、上記で最初にAWS上で試した、登録・取得・全取得など一連の流れがローカルでも試せます。

# 環境変数を設定

せっかくローカルで実行できたので、本番へのデプロイもできるように、ローカルで必要な修正を環境変数に持たせるようにしたいです。
まずは、環境変数を定義している`template.yml`を修正

```yml:template.yml
Transform:
・・・

Globals:
  Function:
    Environment:
      Variables:
        LOCAL_DB_ENDPOINT: ''

Resources:
・・・
``` 

TransformとResourcesの間あたりに、Globals: 〜　を追加

次に`env.json`というファイルがあるので以下のように修正

```json:env.json
{
    "Parameters": {
        "LOCAL_DB_ENDPOINT": "http://dynamodb:8000",
        "SAMPLE_TABLE": "SampleTable"
    }
}
```
`env.json` を準備することでローカルで環境変数を上書きできます。
ローカルで使うDynamoDBのエンドポイントURIとテーブル名を環境変数として設定します。
`dynamodb:8000` は、上記のdocker-compose.ymlで設定した内容です。
`container_name` と`ports` がそのままhost名になります。

### コードも修正

Lambdaのコードも環境変数に合わせて修正

```javascript:src/handlers/get-all-items.js

// Get the DynamoDB table name from environment variables
const tableName = process.env.SAMPLE_TABLE;

// Create a DocumentClient that represents the query to add an item
const dynamodb = require('aws-sdk/clients/dynamodb');

const endpoint = process.env.LOCAL_DB_ENDPOINT
const dynamoOpt = {}
if(endpoint){
  dynamoOpt.endpoint = endpoint
}
const docClient = new dynamodb.DocumentClient(dynamoOpt);

```

### ローカルで実行

ビルドして

```
$ sam build
```

環境変数ファイル付きで実行

```
$ sam local start-api --docker-network lambda-local --env-vars env.json
```

これで、ローカルでの開発環境が整いました。
あとはいろいろいじって試していくだけ。

最初に動くものがサクッとできるので、理解も早いし楽しいです。

# 参考
慣れない環境だったので、かなりいろいろな記事を参考にさせていただきました。
ありがとうございました。

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/console_controlling-access.html
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html
https://qiita.com/ravi_souza/items/b68bf37f76058f724405
https://qiita.com/umeneri/items/6fb3f7560f4a878f6dfd
https://qiita.com/jacoyutorius/items/72984ff07b3b30576d43
https://qiita.com/navitime_tech/items/70432345d930c2bc1a14
https://future-architect.github.io/articles/20200323/
https://selmertsx.hatenablog.com/entry/2019/04/25/SAM_Local%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6Local%E3%81%A7%E5%8B%95%E3%81%8B%E3%81%97%E3%81%A6%E3%81%84%E3%82%8BAWS_Lambda_%E3%81%8B%E3%82%89dynamodb-local%E3%81%AB%E3%82%A2%E3%82%AF