Chalice with DynamoDB

## この記事の目的

この記事の目的は、AWS製のサーバーレスフレームワークChaliceとDynamoDBを組み合わせて利用する手順を個人的なメモとして記すことです。

以下の内容を含みます。

- Chalice, DynamoDBともにAWS上で利用します
- Chalice, DynamoDBLocalを用いてローカルマシンでも利用します（長くなるので次回投稿にします）

以下の内容はカバー出来なかったので今後の課題とします。

- ローカル環境でのIDEを利用したデバッグ
- DynamoDB構築のCDKなどの利用（今回はaws-cliコマンドで実行）
- ユニットテスト

## 前提事項

以下のインストールについては済んでいるものとします。

- Python3
- chalice
- aws-cli
- docker-compose

<details><summary>ちなみに、私の環境は以下のようになっています。(2022/1/24時点）</summary>
- Python 3.9.7 (pyenv利用)
- chalice 1.26.4
- aws-cli 2.4.11
- docer-compose 2.1.1
- VSCode 1.63.2
- macOS Monterey 12.1
</details>

## References

[Chalice公式サイト https://aws.github.io/chalice/index.html](https://aws.github.io/chalice/index.html)


## 構築手順

Project作成

```bash
% chalice new-project chalice_demo
Your project has been generated in ./chalice_demo
```

## AWSにデプロイする

デプロイ

```bash
% chalice deploy
Creating deployment package.
Creating IAM role: chalice_demo-dev
Creating lambda function: chalice_demo-dev
Creating Rest API
Resources deployed:
  - Lambda ARN: arn:aws:lambda:ap-northeast-1:123456789012:function:chalice_demo-dev
  - Rest API URL: https://abcdefghij.execute-api.<region>.amazonaws.com/api/
```

リソースを消す

```bash
% chalice delete
Deleting Rest API: abcdefghij
Deleting function: arn:aws:lambda:ap-northeast-1:123456789012:function:chalice_demo-dev
Deleting IAM role: chalice_demo-dev
```


以下のIAMロールとインラインのポリシーが作成される

- IAM Role  chalice_demo-dev

```JSON
{
  "Version": "2012-10-17",
  "Statement": [ 
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:*:logs:*:*:*"
    }
  ]
}
```

## DynamoDBのテーブルをAWS上に構築する

本来は、CDKなどで実施したいのですが、スタディーが足りないので、今回はaws-cliで作成しました。
詳細は、[README.md](https://github.com/akiraabe/chalice_demo_pj/blob/main/infrastructure/DynamoDB/README.md)を参照ください。

## DBアクセス処理を実装する

- config.jsonの変更
- policy-dev.jsonの追加
- chalicelib/database.pyを追加
- app.pyの変更

**config.json**が重要なので、そこから説明します。
詳しくは、[こちら](https://aws.github.io/chalice/topics/configfile.html)をご覧ください。
ここでのポイントは、環境変数DB_TABLE_NAMEにDynamoDBのテーブル名を設定するところと、autogen_policy(ソースからIAMロールを自動生成する機能)をFalseにしているところ。

```JSON:config.json
{
  "version": "2.0",
  "app_name": "chalice_demo",
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "environment_variables": {
        "DB_TABLE_NAME": "Records"
      },
      "autogen_policy": false
    }
  }
}
```

上で、autogen_plicyをFalseにしているので、ポリシーを手動で定義する。
このファイルのネーミングルールは、policy-<ステージ名>.jsonとなる。

```JSON:policy-dev.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:PutItem",
        "dynamodb:DeleteItem",
        "dynamodb:UpdateItem",
        "dynamodb:GetItem",
        "dynamodb:Scan",
        "dynamodb:Query"
      ],
      "Resource": "*"
    }
  ]
}
```

Pythonのソースコードは長いので、要所だけ記載します。
全体は[GitHub](https://github.com/akiraabe/chalice_demo_pj)を参照ください。


```python:chalicelib/database.py
import os
import boto3
from boto3.dynamodb.conditions import Key,Attr
import uuid

def _get_database():
    endpoint = os.environ.get('DB_ENDPOINT')
    if endpoint:
        print('*** DB_ENDPOINT指定あり ***')
        print(endpoint)
        return boto3.resource('dynamodb', endpoint_url=endpoint)
    else:
        print('*** DB_ENDPOINT指定なし ***')
        return boto3.resource('dynamodb')

def get_record(record_id):
    table = _get_database().Table(os.environ['DB_TABLE_NAME'])
    response = table.query(KeyConditionExpression=Key('id').eq(record_id))
    items = response['Items']
    return items[0] if items else None
```

app.pyにrecords/{record_id}のエンドポイントを追加する例です。

```python:app.py
import os

from chalice import Chalice, NotFoundError, BadRequestError
from chalicelib import database

app = Chalice(app_name='chalice_demo')

@app.route('/records/{record_id}', methods=['GET'], cors=True)
def get_records(record_id):
    record = database.get_record(record_id)
    if record:
        return record
    else:
        raise NotFoundError('Record not found.')
```

## 再度デプロイし動作確認します。

再度``` chalice deploy ```を行い、エンドポイントを呼び出します。

```bash
# 結果は見やすく整形しています。
% curl https://abcdefghij.execute-api.<リージョン名>.amazonaws.com/api/records/id2
{
  "result_time":"00:23:11",
  "race":"3",
  "section":"2",
  "runner_name":"test-runner",
  "team":"TeamA",
  "description":"Memo2",
  "id":"id2",
  "sub":"LOCAL_USER"
}

```

## まとめ

今回は、Chalice with DynamoDBということで、簡単な処理を実装してみました。
Chaliceで基本構成のAPI Gatewayは簡単に作成できますが、いろいろなリソースとの連携は、Chaliceだけだと限界がある気はします。
それもあってか、Chaliceは最近ではCDKとの連携もできるようです。
[https://aws.github.io/chalice/tutorials/cdk.html](https://aws.github.io/chalice/tutorials/cdk.html)
今回は採用していませんが、そのうちに、他のAWSサービスとの連携に使ってみたいと思います。

次回は、Chalice with DynamoDBのローカル実行について書きたいと思います。