API Gateway + Lambda + DynamoDBでサーバーレス環境を構築する

# API Gateway + Lambda + DynamoDB = Serverless

AWSでサーバーレスを構築するアーキテクチャを考えた時に、もっとも基本的でわかりやすい構成は、API Gateway + Lambda + DynamoDB(ないしはS3)の組み合わせだと思います。

## このエントリーで触れること
- Lambda関数の作り方
- API Gatewayでのリソースの作り方
- API GatewayとLambda関数の紐づけ方
- Lambda関数でのDynamoDBのいじり方

AWS SAMには触れないです。

## Lambdaで関数を作る

Lambdaで関数を作ってデプロイするのは非常に簡単な作業です。

ランタイムはnode.jsを使います。

関数を作成する方法としては、
1. AWSコンソール上で関数を作成する
2. ローカルで関数を作成してzip化し、Lambdaにアップロードする
3. ローカルで関数を作成してzip化し、S3にアップロードした後に、Lambdaからそれを呼ぶ
4. ローカルで関数を作成してzip化し、cliでデプロイする
の4種類の方法があり、もっとも簡便に作るのであれば1で事足ります。

しかし、もっと込み入った内容のロジックが必要なのであれば、2,3,4の方法を選びます。

たとえば、node_modulesを入れてyarnでインストールしてきたライブラリを使いたいとか、lambda上ではできないけどローカルでは用意できるようなものを設定したい時には、2,3,4の方法でデプロイした方が良さそうです。

ここでは1と2,3,4の関数作成方法に分けて簡単に関数を作ってみます。

### AWSコンソール上で関数を作成

AWS Lambdaのページからサイドペインの「関数」を選択すると、関数が一覧で表示されます。
この一覧のヘッダー部分に以下のように関数の作成ボタンがあります。

<img width="1099" alt="Screenshot 0031-11-05 at 10.42.09 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/8eb78021-9af5-85bd-ffc4-42858647d4ab.png">

ボタンクリック後の設定では、

- 一から作成（選択）
- 関数名（入力）
- ランタイム（ここではnode.js10.x） （選択）
- 基本的なLambdaアクセス宣言で新しいロールを作成（選択）

「関数の作成」からLambda関数を作成します。

すると、作成した関数の詳細ページへ飛び、関数のコードを編集することができます。

<img width="1252" alt="Screenshot 0031-11-12 at 9.37.29 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/fe3c84a2-d954-aa55-6c3b-9b5fcd80e23f.png">

デフォルトのコードでは、'Hello from Lambda!'の文字列がstatusCode200で返ってくるようになっています。
ここでrequestを受け取って処理するなどを行うためには、この後説明するAPI Gatewayの設定を行う必要があります。

### ローカルで関数を作成してLambdaにアップロード

```
$ mkdir myFunction | cd myFunction
$ yarn
$ yarn add moment
$ vim index.js
```

index.jsの中身は適当にこんな感じで:point_down_tone2:

<img width="689" alt="Screenshot 0031-11-12 at 9.34.11 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/c7cf1dc1-d148-af15-7ca1-7cab2f2cea98.png">


コードが書けたらnode_modulesと同じレベルのディレクトリ内でzipコマンドを実行します。

```
$ tree
.
├── index.js
├── node_modules // 以下省略
├── package.json
└── yarn.lock
$ zip myFunction . -r
$ ls
index.js       myFunction.zip node_modules   package.json   yarn.lock
```

解凍した時に上記の内容を含んだmyFunctionディレクトリがトップレベルになる必要があるのでzipコマンドには-rオプション(recurse into directory)をつけています。

zipができたらLambdaのコンソールに移動して、以下のコードエントリタイプのドロップダウンから「.zipファイルをアップロード」を選択します。

<img width="821" alt="Screenshot 0031-11-05 at 11.14.08 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/4b4780a6-3e4f-f4be-2c57-c77ec09ad690.png">

アップロードボタンが出てくるのでそこから上で作成したzipをアップロードしてみましょう。
保存すると、関数コード欄のソースコードがローカルで作成したコードに書き換わります。

インストールしておいたmomentも使えるようになり、エディターのサイドペインにnode_modulesが追加されていると思います。
コンソール上だけだとnode_modulesを利用することができませんが、このようにローカルで作成してからアップロードするとコンソール上でもnode_modulesを扱えるようになります。

<img width="1260" alt="Screenshot 0031-11-05 at 11.23.16 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/71e2d80b-df1c-5e4a-b33b-e945cf0e7991.png">


### ローカルで関数を作成してS3にアップロード

Lambdaに直接zipをアップロードすることもできますが、S3にアップロードしたzipを参照することもできます。
Lambda上に表示される注意書きにもありますが、大きなサイズのzipをアップロードする際にはS3経由でLambdaにデプロイするのが望ましいようです（具体的には10MB以上はS3の利用を検討した方が良いらしいです）。

上記で作成したmyFunction.zipを使いまわします。

```
$ zip myFunction . -r
```

でmyFunction.zipを作成したのち、S3コンソールを開きます。

適当な名前のバケットを作成して、その中にmyFunction.zipをアップロードします。

<img width="1305" alt="Screenshot 0031-11-12 at 9.43.06 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/d9ad7cad-bf41-e842-d453-9998fe387d9f.png">

次にLambdaコンソールを開きます。
関数コードの「AmazonS3からのファイルのアップロード」を選択して、S3にアップロードしたzipのオブジェクトURLを入力します。

<img width="1297" alt="Screenshot 0031-11-12 at 9.46.42 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/068bbdf7-145a-8ec4-0c35-c8239bd9b391.png">

あとは関数を保存するだけです。
テストを実行すると、レスポンスが返ってきます。

<img width="1254" alt="Screenshot 0031-11-12 at 9.48.54 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/0c2d5cc8-570f-6dd0-fe15-a4c82d1acec9.png">


### ローカルで関数を作成してCLIでデプロイ

CLIで関数を作成する場合は、create-functionを利用します。
https://docs.aws.amazon.com/cli/latest/reference/lambda/create-function.html

```
  create-function
--function-name <value>
--runtime <value>
--role <value>
--handler <value>
[--code <value>]
[--description <value>]
[--timeout <value>]
[--memory-size <value>]
[--publish | --no-publish]
[--vpc-config <value>]
[--dead-letter-config <value>]
[--environment <value>]
[--kms-key-arn <value>]
[--tracing-config <value>]
[--tags <value>]
[--layers <value>]
[--zip-file <value>]
[--cli-input-json <value>]
[--generate-cli-skeleton <value>]
```

また、関数を更新する場合には、update-function-codeかupdate-function-configurationを利用します。
https://docs.aws.amazon.com/cli/latest/reference/lambda/update-function-code.html
https://docs.aws.amazon.com/cli/latest/reference/lambda/update-function-configuration.html

update-function-codeはlambda関数のコードを更新するコマンドで、
update-function-configurationはlambda関数の設定(ランタイムや環境変数)を更新するコマンドです。

他のコマンドについては[CLI Command Reference](https://docs.aws.amazon.com/cli/latest/reference/lambda/index.html)を参照してください。

さて、デプロイした後はそれだけでは関数は使えませんので、API Gatewayを介して呼び出せるように設定します。


## API GatewayからLambda関数を利用する

API GatewayとLambdaの関係性は、

- API Gateway
 クライアントからの通信を受け付けてLambda関数にリクエストを渡す。
 また、そこから受け取ったレスポンスをクライアントに返却する。
 (API GatewayはLambda以外にも既存のHTTPエンドポイントやSQSやSNSなどのAWSリソースと連携させることもできます)

- Lambda
 API Gatewayから受け取ったリクエストを基に処理を実行し、レスポンスを返却する。

<img width="1019" alt="Screenshot 0031-10-27 at 7.58.53 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/b1b8a090-738e-6cfe-cb26-08e9b0502d26.png">

#### ①　メソッドリクエスト
クエリーのパラメータ等を設定する。

#### ②　統合リクエスト
API Gatewayのリソースが呼び出す対象を設定する（ここではlambda関数となる）。
lambdaへ送るbodyパラメーターのテンプレートを設定する。

#### ③　統合レスポンス
lambdaから返ってきたレスポンスをAPI Gatewayのレスポンス設定にマッピングする。

#### ④　メソッドレスポンス
API Gatewayがクライアントへ返すステータスコードごとのレスポンスヘッダーやボディを設定する。

### API Gatewayでエンドポイントを作る

API Gatewayの「APIの作成」からAPIを作っていきます。
今回はプロトコルをRESTとし、名前はmyAPIとします。

<img width="1225" alt="Screenshot 0031-11-12 at 10.05.19 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/8660ea1d-87d0-416b-657f-087c1c75f575.png">

作成されたmyAPIのリソース画面で、「アクション」のドロップダウンをクリックするとアクション一覧が表示されます。
ここでリソースを作成します。

<img width="1391" alt="Screenshot 0031-11-12 at 10.05.53 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/ce5b4887-1d89-c7c9-f159-b6b5e8acfab5.png">

リソース名を入れて「リソースの作成」を行います。

<img width="1178" alt="Screenshot 0031-11-12 at 10.18.59 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/d04922eb-c97f-afa0-4e05-8b03c63797a8.png">

そして、作成したリソースにメソッドを追加していきます。
「アクション」ドロップダウンからメソッドの作成を選択し、メソッドの種類を選択します。
ここではGETを選択してチェックします。

<img width="495" alt="Screenshot 0031-11-12 at 10.19.48 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/e386c27c-2b93-b199-a5bc-7ac3409338cc.png">

GETメソッドの統合タイプにLambda関数を選択し、
Lambda関数に上記で作成したmyFunction関数を選択します。

<img width="1179" alt="Screenshot 0031-11-12 at 10.21.50 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/b5d7a825-a4d0-cb22-294e-0d967b7406cc.png">

保存すると以下のような画面が表示されます。
ここで前述したメソッドリクエスト、統合リクエスト、統合レスポンス、メソッドレスポンスを設定します。

<img width="1176" alt="Screenshot 0031-11-12 at 10.23.11 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/a732c020-087b-8e00-e0ac-7628cc8abfe9.png">

今回冗長となるのでこれらの細かい説明は省きます。

あとはAPIをデプロイして、作成されるエンドポイント/myresourceにアクセスしてみましょう。
デプロイ時のステージなどは好きなように設定してみてください。

<img width="1173" alt="Screenshot 0031-11-12 at 10.28.12 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/d28e4fc7-413f-0ff3-57fb-7454cf01af2c.png">

すると、先ほど作成したLambda関数のレスポンスがブラウザに表示されるはずです。

<img width="610" alt="Screenshot 0031-11-12 at 10.29.14 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/1fa3f61c-e511-f185-bd24-c3668dd3f11a.png">

## LambdaからDynamoDBに接続する

次はLambdaからDynamoDBの値を取得して、その値をクライアントへ返せるようにします。

### DynamoDBにデータを作る

DynamoDBのダッシュボードから「テーブルの作成」をクリックします。

<img width="932" alt="Screenshot 0031-11-12 at 10.35.39 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/f4baaaab-0cdc-c6a7-791e-fe3cb557e75c.png">

パーティションキー等の設計については[DynamoDBのベストプラクティス](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/best-practices.html)を参考にしてみてください。

作成したテーブルに項目を追加します。
「項目の作成」からパーティションキーの値の他に任意の項目を追加します。

<img width="927" alt="Screenshot 0031-11-12 at 10.40.10 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/c746f773-c1b4-30d0-ffa7-984f69489203.png">

これでデータが用意できたので、このテーブルにLambdaから接続して値を取得できるようにします。

以下のようなjsファイルを用意し、zip化します。

```js
const AWS = require('aws-sdk')
const moment = require('moment')
const dynamo = new AWS.DynamoDB.DocumentClient({region: 'ap-northeast-1'})

exports.handler = async (event, context) => {
  const params = {
    TableName: "testTable",
    Key:{
      uuid: "7dd1b5f4-bb05-4a87-b464-5ddf88254837"
    }
  }
  await dynamo.get(params, (err, data) => {
    if (err) context.fail(err)
    context.succeed({
      statusCode: 200,
      body: data,
      time: moment().format('YYYY/MM/DD')
    })
  }).promise()
}
```

```
$ tree
.
├── index.js
├── node_modules // 以下省略
├── package.json
└── yarn.lock
$ zip myFunction . -r
$ ls
index.js       myFunction.zip node_modules   package.json   yarn.lock
```

直接LambdaにアップロードでもS3にアップロードしてそれを参照するのでも良いです。
アップロード後に保存してテストを実行してみてください。

<img width="1252" alt="Screenshot 0031-11-13 at 12.27.32 AM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/6f54c387-fe50-da12-8f7f-8971e6e8efcb.png">

こんな感じでレスポンスが返ってきたら成功です。
この関数を紐づけたAPI Gatewayのエンドポイントに接続してみてください。

<img width="920" alt="Screenshot 0031-11-13 at 12.28.57 AM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245704/38903089-4cb0-72b2-1f46-39e5eb293ba1.png">

DynamoDBから取得したデータがブラウザに表示されると思います。

もっと応用的に、リクエストを受け取って任意のデータを取得したいような場合には、統合リクエストのマッピングテンプレート等を設定して、Lambdaに渡るeventの中身を定義する必要がありますが、ここでは省きます。

ここまでで以下の項目について触れてきました。

- Lambda関数の作り方
- API Gatewayでのリソースの作り方
- API GatewayとLambda関数の紐づけ方
- Lambda関数でのDynamoDBのいじり方

上でやったようにコンソールで色々と操作しましたが、AWS SAMを使えばコマンドラインでサーバーレスを構築する構築することもできます。
ローカルでテストができるのは気が楽ですね:blush: