ローカル API Gateway + Lambda (SAM CLI) + MySQL(Docker)環境構築方法メモ


* ローカル環境でAPI Gateway + Lambda + MySQL構成の開発環境を構築する方法についてメモする。

## 事前準備

* 下記要素をインストールしておく。
    * SAM CLI
    * Docker, docker-compose

* SAM プロジェクトを作成する。
    * ひな形作成
 
      ```shell
      sam init
      ```

      ※ランタイムはPython3.9を選択する

      ※Hello World Exampleを利用する

  

## 構成

```
project
├─start.sh
├─sam-app
│  ├─template.yaml
│  ├─env.json

......
│  └─hello_world
│    └─app.py   
│    └─requirements.txt 
└─db
    ├─docker-compose.yml
    ├─initdb.d
    |	└─init.sql   
	├─db      
    │  ├─data
    │  └─my.cnf
    └─mysql
        └─data
```



## コード

### db

* `docker-compose.yml`

  * `test_db`DBを立ち上げる
  * `initdb.d`ディレクトリ配下の「.sql」ファイルを実行する

  ```yaml
  version: "3.3"
  networks:
    container-link:
      name: docker.internal
  services:
    db:
      image: mysql:5.7
      container_name: db
      environment:
        MYSQL_ROOT_PASSWORD: rootpass
        MYSQL_DATABASE: test_db
        MYSQL_USER: mysqluser
        MYSQL_PASSWORD: mysqlpass
      networks:
        - container-link
      volumes:
        - ./db/data:/var/lib/mysql
        - ./db/my.cnf:/etc/mysql/conf.d/my.cnf
        - ./initdb.d:/docker-entrypoint-initdb.d
      ports:
        - 3308:3308
      command: --port 3308
      tty: true
  ```

* `initdb.d/init.sql`

  * `test_table`テーブルを作成し、初期データを登録する

  ```sql
  CREATE TABLE `test_db`.`test_table` (
    `seq` INT NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(45) NULL,
    `age` INT NULL,
    PRIMARY KEY (`seq`));
  
  INSERT INTO `test_db`.`test_table` (`name`, `age`) VALUES ('Tanaka Taro', '20');
  INSERT INTO `test_db`.`test_table` (`name`, `age`) VALUES ('Yamada Jiro', '30');
  ```

### sam-app

* `app.py`

  * MySQL`db.test_db.test_table`からのデータ取得結果を返却する

  ```python
  import json
  import mysql.connector
  import os
  def lambda_handler(event, context):
      # MySQLからデータ取得
      sql = "select * from test_table;"
      # 接続先情報は環境変数から読み込む
      conn = mysql.connector.connect(
          host = os.environ['MYSQL_HOST'],
          port = os.environ['MYSQL_PORT'],
          user = os.environ['MYSQL_USER'],
          password = os.environ['MYSQL_PASSWORD'],
          database = os.environ['MYSQL_DATABASE']
      )
      cur = conn.cursor(dictionary=True)
      cur.execute(sql)
      results = cur.fetchall()
      cur.close()
      conn.close()
  	# レスポンスボディ生成
      user_list = []
      for result in results:
          user_list.append({
                  "name":result["name"],
                  "age":result["age"]
              }
          )
      response_dict = {}
      response_dict['total'] = len(user_list)
      response_dict['items'] = user_list
      
      return {
          "statusCode":200,
          "body":json.dumps(response_dict)
      }
  ```

* `requirements.txt`

  * MySQLコンテナ接続のための`mysql-connector-python`を追加する

  ```
  requests
  mysql-connector-python
  ```

* `template.yaml`

  * 環境変数読み込み用に以下を設定する

  ```yaml
  ...
  Globals:
    Function:
      Timeout: 3
      Environment:
        Variables:
          MYSQL_HOST: MYSQL_HOST
          MYSQL_DATABASE: MYSQL_DATABASE
          MYSQL_PORT: MYSQL_PORT
          MYSQL_USER: MYSQL_USER
          MYSQL_PASSWORD: MYSQL_PASSWORD
  ```

* `env.json`

  * DB接続情報を記述する

  ```json
  {
      "Parameters": {
          "MYSQL_HOST":"db",
          "MYSQL_DATABASE":"test_db",
          "MYSQL_PORT":3308,
          "MYSQL_USER":"mysqluser",
          "MYSQL_PASSWORD":"mysqlpass"
      }
  }
  ```

  

### その他

* `start.sh`
    * 起動用スクリプト

  ```shell
  # DB起動
  cd db
  docker-compose up -d
  # アプリ起動
  cd ../sam-app
  sam build --use-container
  sam local start-api --docker-network docker.internal --env-vars env.json
  ```

  

## 動作確認

* 以下のコマンドを実行する

  ```shell
  bash start.sh
  ```

  ※MySQLコンテナ起動(テーブル作成+初期データ登録) → SAM起動が行われる

* 以下のリクエストを実行する

  ```http
  GET /hello HTTP/1.1
  Host: 127.0.0.1:3000
  ```

  以下のレスポンスが返却される

  ```json
  {
      "total": 2,
      "items": [
          {
              "name": "Tanaka Taro",
              "age": 20
          },
          {
              "name": "Yamada Jiro",
              "age": 30
          }
      ]
  }
  ```