Chaliceを使って爆速でAPIサーバを構築するやつをやってみた

# 概要
* AWS純正のフレームワークであるChaliceを利用して、爆速でAPIサーバを構築しようという話です
* 探せばいくらでも記事が出てくるのですが、情報が散見してるのでまとめてみた

# Chaliceとは？
* サーバーレスアプリケーションのためのフレームワーク
* サーバレス環境が簡単に構築できるよ
* コンソールぽちぽちする必要もないし、ymlファイルを書いたりする必要もない

# 前提
* ~/.aws/config と~/.aws/credential の設定を行なっていること
* pythonが使える環境であること(3.9系で動かしています)
* APIサーバに限らず、他のAWSサービスと連携してもっと色々なこともできるようなのですが、今回は割愛します

# 手順
## 基本
**①chaliceをインストール**
```
$ pip install chalice
```

**②プロジェクト作成**
```
$ chalice new-project helloworld
```

こんな感じのディレクトリ構成のフォルダが出来上がると思います
```
.
├── .chalice
│   └── config.json
├── .gitignore
├── app.py
└── requirements.txt
```

**③ローカル環境でアプリを実行**
ルートディレクトリで以下を実行
```
$ chalice local
Serving on http://127.0.0.1:8000
```

ブラウザでアクセスしてみると {"hello": "world"} が表示されるはずです。

## ディレクトリ構成を変えてみる
* **app.py**の中身は以下のようになっています

```
from chalice import Chalice

app = Chalice(app_name = 'helloworld')

@app.route('/')
def index():
    return {'hello': 'world'} 
```

* この状態だと全てのコードを **app.py** に記載しなければなりません。
* そこで、**chalicelib** を使ってディレクトリ構成を変えてみます
  * chaliceでは、**chalicelib** と名付けたディレクトリ以下にモジュールを配置できます。
  * ここに配置したモジュールはapp.pyでimportして使えるので、共通処理や各APIを切り分けて実装可能です。
* ↓こんな感じで運用するのがいいかもしれない

```
.
├── .chalice
│   ├── config.json
│  └──  custom-policy.json
├── .gitignore
├── app.py
├── chalicelib
│   ├── common
│   │   └── common.py
│   └── function
│       ├── get_device_api.py
│       └── put_device_api.py
└── requirements.txt
```

* ↓importするときはこんな感じ

```
from chalice import Chalice
from chalicelib.function import get_device_api, put_device__api
from chalicelib.common import common

app = Chalice(app_name = 'helloworld')

@app.route('/device', methods=['GET'])
def get_device():
    res = get_device_api.get_device()
    return res

@app.route('/device', methods=['PUT'])
def put_device():
    res = put_device_api.put_device()
    return res

```

## デプロイ時の環境設定方法
* .chalice/config.jsonから、デプロイする環境ごとにアプリ内で使用したい環境変数を設定することが可能
* **stages** に記載するステージ名は自由に決められます（**stg1** とか **stg2** でもよい）

```
{
  "version": "2.0",
  "app_name": "demandsrv-api",
  "stages": {
    "dev": {
      "api_gateway_stage": "api-dev",
      "lambda_functions": {
        "api_handler": {
          "environment_variables": {
            "HOST_NAME": "***.***.jp",
            "REGION_NAME": "ap-northeast-1",
            "SECRET_NAME": "arn:aws:******"
	        }
	      }
      }
    },
    "stg": {
      "api_gateway_stage": "api-stg",
      "lambda_functions": {
        "api_handler": {
          "environment_variables": {
            "HOST_NAME": "***.***.jp",
            "REGION_NAME": "ap-northeast-1",
            "SECRET_NAME": "arn:aws:******"
	        }
	      }
      }
    },
    "prod": {
      "api_gateway_stage": "api-prod",
      "lambda_functions": {
        "api_handler": {
          "environment_variables": {
            "HOST_NAME": "***.***.jp",
            "REGION_NAME": "ap-northeast-1",
            "SECRET_NAME": "arn:aws:******"
	        }
	      }
      }
    }
  }
}
```

* 実際にデプロイするときは、↓のようにoptionをつける

```
$ chalice local --stage dev
```

## @app.routeについて
* @app.route()デコレータ直下にあるメソッドが、このパスのリクエストに対するエントリポイントとなる（Flask？）
* ()内の引数に色々設定が可能
* 例えば以下は、GETメソッドの*devices*APIにcorsの許可設定とapiKey設定をしています

```
@app.route('/devices', methods=['GET'], cors=True, api_key_required=True)
```

* 注意：*api_key_required=True* とすることでapiキー必須になるのですが、肝心のAPIキーや使用量プランは手動で作って紐づける必要があります。なので0から環境構築するときはその辺の設定が必要 
* 一度設定してしまえばそれ以降は設定不要。

## 権限周りの注意点
* Chaliceはデプロイを実行するIAMユーザの権限を使って各種サービスにアクセスする権限を付与します。
* 従って、IAMユーザーにはIAM ロールを作成・編集する権限が必要なので、客先アカウントなどでの運用には注意が必要（IAMポリシーとかロールとかの権限付与したくないという顧客の場合は、、残念😿）
* boto3 の Client class を使うか、 Resource class を使うかによって、付与される権限が異なる
  * 個人的にResource classの方が圧倒的に使いやすいんですが、chaliceはデプロイするときにClient classの API を読んでいるかどうかを解析して自動で権限の付与を行なっているようです。
参考：https://michimani.net/post/aws-about-auto-generate-iam-policy-in-chalice/
  * なので、Resource classを使う場合は、.chalice/config.jsonを以下のように設定して、自前のポリシーを設定する必要があります

```
{
  "version": "2.0",
  "app_name": "chalice-sample",
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "autogen_policy": false,
      "iam_policy_file": "custom-policy.json"
    }
  }
}
``` 

* .chalice/custom-policy.jsonを作る（以下参考）

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

## 所感
* 一つの関数としてパッケージ化されてデプロイされるので、ファイルがでかいとコンソール上で中身を見れない
* cloudwatch logsもAPIごとに分けて作成されないので、全てのAPIが一つのログストリームに出力される
* APIとしての用途以外の関数（アラートとかスケジューリング用）をデプロイするというようなことができない（調べた限りでは無理そう）
* 自由度高めな構成で使いたい場合にはSAMとかのほうが良さそう