AWSのLambdaとDynamoDBのローカル開発（SAM）

## 概要

AWSのコンソールで割と簡単にLambdaの処理とDynamoDBのテーブルを作成したり、編集したりすることができます。

ただ、チームでやるプロジェクトだと、バージョン管理とローカルのテストが重要になってきます。

SAMのCLIを使うと本物のコンソールを使わなくても、ローカルで動作確認もできますし、簡単にAWSにデプロイもできます。

## 環境の情報

- Windows 10
- Docker for Windows
- Visual Studio Code
- Python 3.7  *(他のでも大丈夫だと思います)*

## プロジェクト作成

### プロジェクトフォルダーを作成し、VSCodeで開きます。

### Pythonの確認

VSCodeのターミナルは【Ctrl+@】で開けます。

```
sam-lambda-dynamodb> python -V
Python 3.7.2
```
### pipの更新

```
sam-lambda-dynamodb> python -m pip install --upgrade pip
```
### 仮想環境の作成

仮想環境を作ることで、別の環境で構築する時に、Pythonのインストールされているライブラリーのバージョンを簡単に合わせることができるので、ライブラリーのバージョンの違いで問題になることはなくなります。

```
sam-lambda-dynamodb> python -m venv venv
sam-lambda-dynamodb> .\venv\Scripts\activate
(venv) sam-lambda-dynamodb>
```

仮想環境に入っていると、左側に（venv）が表示されます。


activateスクリプトを実行できない場合、管理者として以下のコマンドを実行します。

```
set-executionpolicy remotesigned
```

### 仮想環境の中のpipの更新

```
(venv) sam-lambda-dynamodb> python -m pip install --upgrade pip
```

### VSCodeのWorkspaceの設定

仮想環境のための設定を入れます。
- VSCODEに「Python」というExtensionをインストールします。
- 「F1」を押し、`>Preferences: Open Workspace Settings`　を書きます。
- 「Python: Python Path」の設定を探し、仮想環境のpython.exeのパスを入力します。
	- 例：`C:\\Users\\path\\to\\sam-lambda-dynamodb\\venv\\Scripts\\python.exe`
- 「Python: Venv Path」の設定を探し、仮想環境のベースパスを入力します。
  - 例：`C:\\Users\\path\\to\\sam-lambda-dynamodb\\venv`
- 設定終わると、ワークスペースフォルダーに`.vscode/settings.json`が自動的に作成されます。
	- `settings.json`の中身は以下のようになります。

```json:settings.json
{
	"python.pythonPath": "C:\\Users\\path\\to\\sam-lambda-dynamodb\\venv\\Scripts\\python.exe",
	"python.venvPath": "C:\\Users\\path\\to\\sam-lambda-dynamodb\\venv"
}
```

### 仮想環境に必要なライブラリーをインストール

```
(venv) sam-lambda-dynamodb> pip install aws-sam-cli
(venv) sam-lambda-dynamodb> sam --version
SAM CLI, version 0.11.0
(venv) sam-lambda-dynamodb> pip install awscli
(venv) sam-lambda-dynamodb> aws --version
aws-cli/1.16.101 Python/3.7.2 Windows/10 botocore/1.12.91
```

他の環境でも同じライブラリーのバージョンになるため、`requirements.txt`を作成します。

```
(venv) sam-lambda-dynamodb> pip freeze | Out-File -Encoding UTF8 .\requirements.txt
```

`requirements.txt`からインストールしたい時、このコマンドでできます。
「`pip install -r requirements.txt`」

### SAMのinit処理を実行

```
(venv) sam-lambda-dynamodb> sam init --runtime python3.7
```
こちらのコマンドを実行すると、`sam-app`のフォルダーが作成されます。
**hello_world**のデフォルトプロジェクトは設定されているので、試してみましょう。

### awsの設定

ローカル用のテスト環境なので、内容はあまり重要ではないです。

```
(venv) sam-lambda-dynamodb> aws configure
AWS Access Key ID [None]: testid
AWS Secret Access Key [None]: testsecret
Default region name [None]: ap-northeast-1
Default output format [None]: json
```

### hello_worldのテンプレートのテスト

- `sam-app`に入り、ビルドを実行

```
(venv) sam-lambda-dynamodb> cd .\sam-app\
(venv) sam-lambda-dynamodb\sam-app> sam build
```

- dockerは実行中か確認し、APIを実行

```
(venv) sam-lambda-dynamodb\sam-app> sam local start-api
```

- hello_worldのapiを実行

ブラウザーで「**localhost:3000/hello**」にアクセスします。

画面に`{"message": "hello world"}`が表示されたら成功です。

### ローカルのDynamoDBの設定

「**Ctrl+C**」でhello_worldのlambdaを止めます。

DynamoDBをプルします。

```
(venv) sam-lambda-dynamodb\sam-app> docker pull amazon/dynamodb-local
```

lambdaとdynamodbは同じネットワークにあると楽なので、dockerのネットワークを作成します。

```
(venv) sam-lambda-dynamodb\sam-app> docker network create lambda-local
```

containerを作成し、runします。

```
(venv) sam-lambda-dynamodb\sam-app> docker run --network lambda-local --name dynamodb -p 8000:8000 amazon/dynamodb-local -jar DynamoDBLocal.jar -sharedDb
```

runの後は「**Ctrl+C**」を押します。

PS: 上のコマンドを１回目の時にしか使わないです。次の実行の時「`docker start dynamodb`」のコマンドを使います。

### DynamoDBのテーブルを作成

- プロジェクトフォルダに「DynamoDB」のフォルダを作成し、AccessTable.jsonのファイルを作成します。

```json:AccessTable.json
{
  "TableName": "Access",
  "KeySchema": [
    {
      "AttributeName": "Path",
      "KeyType": "HASH"
    },
    {
      "AttributeName": "Date",
      "KeyType": "RANGE"
    }
  ],
  "AttributeDefinitions": [
    {
      "AttributeName": "Path",
      "AttributeType": "S"
    },
    {
      "AttributeName": "Date",
      "AttributeType": "S"
    }
  ],
  "ProvisionedThroughput": {
    "ReadCapacityUnits": 2,
    "WriteCapacityUnits": 2
  }
}
```

- プロジェクトのルートフォルダーに戻り、テーブルの作成コマンドを実行します。

```
(venv) sam-lambda-dynamodb\sam-app> cd ..
(venv) sam-lambda-dynamodb> aws dynamodb create-table --cli-input-json file://.\DynamoDB\AccessTable.json --endpoint-url http://localhost:8000
(venv) sam-lambda-dynamodb> aws dynamodb list-tables --endpoint-url http://localhost:8000
{
    "TableNames": [
        "Access"
    ]
}
```

これでテーブルの作成ができました。

### 「hello_world」のapiを編集

まず、ローカルと本番の接続処理は違うので、ローカルだとわかるように`template.yaml`に環境のパラメーターを追加します。

実際にデプロイをする時パラメーターを上書きできます。

```diff:template.yaml
... 略

+Parameters:
+    Env:
+        Type: String
+        Default: local

Globals:
    Function:
        Timeout: 3
+       Environment:
+           Variables:
+               ENV: !Ref Env

... 略
```

「hello_world」のフォルダーの中にある`requirements.txt`に`boto3`のライブラリーを追加します。

```requirements.txt
requests
boto3
```

「hello_world」のフォルダーの中にある`app.py`を編集します。

returnの前に以下のコードを追加します。

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

# ... 略

try:
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    session = boto3.session.Session()
    awsRegion = session.region_name
    paramList = event['queryStringParameters']

    client = boto3.client('dynamodb')
    # ローカルの場合
    if os.environ['ENV'] == 'local':
        dynamodb = boto3.resource('dynamodb', region_name = awsRegion, endpoint_url = "http://dynamodb:8000")
    # ローカル以外の環境の場合
    else:
        dynamodb = boto3.resource('dynamodb', region_name = awsRegion)
    # テーブルを取得
    table = dynamodb.Table('Access')
    # 日時の文字列
    date = datetime.utcnow().isoformat()
    # 登録するアイテムのベース
    item = {'Path': event['path'], 'Date': date}

    if paramList != None:
        for key,value in paramList.items():
            item[key] = value

    table.put_item(
        Item=item
    )

except Exception as e:
    # Lambdaログにエクセプションの情報を入れる
    logger.exception(e)
    return {
        'statusCode': 500,
        'body': json.dumps({
            'error_message': str(e)
        }),
    }

# ... 略
```

### APIを試す

もう一度「`sam-app`」に入り、ビルドを実行します。

```
(venv) sam-lambda-dynamodb> cd .\sam-app\
(venv) sam-lambda-dynamodb\sam-app> sam build
```

APIを`lambda-local`のネットワークで開始します。

```
(venv) sam-lambda-dynamodb\sam-app> sam local start-api --docker-network lambda-local
```

ブラウザーでGatewayにアクセスします。(**http://localhost:3000/hello?param=test**)

成功できたら、以下のコマンドでテーブルのアイテムが見れます。

```
(venv) sam-lambda-dynamodb> aws dynamodb scan --table-name Access --endpoint-url http://localhost:8000
{
    "Items": [
        {
            "Path": {
                "S": "/hello"
            },
            "param": {
                "S": "test"
            },
            "Date": {
                "S": "2019-02-11T03:48:44.017178"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}
```

これでDynamoDBとLambdaをローカルで使えるようになりました。

### その他の情報

- **CloudFormation**のテンプレートに使える定義をSAMのテンプレートにも使えます。
- git管理をする場合、仮想環境のフォルダーをgitignoreに入れます。
- SAMのテンプレートのドキュメンテーション:
  - https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md
- boto3のDynamoDBのドキュメンテーション：
  - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html
- このプロジェクトのgit:
  - https://github.com/toritroniks/sam-lambda-dynamodb