API GatewayとLambdaとDynamoDBを使ってサーバレス・最短で登録フォームを作る

API GatewayとかLambdaでサーバレスな話は既にたくさん記事はあるので、詳しくはそちらをご覧ください。
この記事は個人的な備忘録的に記載しています。

# 試したこと

API GatewayとAWS LambdaとDynamoDBだけを使って、コメント登録サイトを作ってみました。
構成は以下のような感じです。

![serverless_arch.png](https://qiita-image-store.s3.amazonaws.com/0/45851/30f73959-9546-f4ff-3561-0d64f41ae711.png)


API GatewayにGETリクエストでまずは登録済みのコメント情報を取得してHTML表示をし、その中のFormからAPI GatewayにPOSTリクエストを送り、新たなコメントを新規登録するという流れです。
本来ならS3とか、他のサイト上にFormとか作って、API Gatewayを呼び出すという感じでしょうが、ちょっとAPI
 Gatewayの機能のお試し用で作ったので、API Gatewayのマッピングテンプレートを使ってHTML生成までAPI Gateway側で完結させるようにしています。

出来上がりはこんな感じです。

![投稿フォーム.png](https://qiita-image-store.s3.amazonaws.com/0/45851/dd0d09e3-b58e-fa7a-419a-6822fd5cebed.png)


では、以下、試したことをまとめておきます。

# IAMロール作成

LambdaからDynamoDBにアクセスして操作する権限を持ったロールを作成します。
IAMの管理画面から、Lambdaサービスに紐付くロールを作成し、「AmazonDynamoDBFullAccess」ポリシーを適用しておきます。

![qiita_iam_create1.png](https://qiita-image-store.s3.amazonaws.com/0/45851/e65dccf7-e6f3-5129-b7b1-a15f834f3a7f.png)
![qiita_iam_create2.png](https://qiita-image-store.s3.amazonaws.com/0/45851/4ef87414-6a9c-61ff-1e70-8d598e8a8b2b.png)

# データ格納先のDynamoDBを設定

以下の情報で新規にテーブルを作成します。

* テーブル名: Sample
* プライマリキー: timestamp (数値)

これだけで完了。

# Lambda function作成
今回は、Python3.6でfunctionを作成。
作成したLambda functionは2つ。

* SampleGetFunc: GETリクエスト受信時にDynamoDBからデータをScanして返す関数
* SamplePostFunc: POSTリクエスト受信時にDynamoDBにデータをPutItemして、登録後にScanして返す関数

各処理には、先程作成したIAMロールを割り当てておきます。
処理内容はそれぞれ以下のような感じです。

```py:SampleGetFunc
import datetime
import boto3
from boto3.session import Session

def lambda_handler(event, context):
    region = "ap-northeast-1"
    session = Session(
        region_name=region
    )
    dynamodb = session.resource('dynamodb')

    table = dynamodb.Table('Sample')

    scan_response = table.scan()
    scan_response['Items'] = sorted(scan_response['Items'], key=lambda x:x['timestamp'], reverse=True) # scanしたデータをtimestampで降順にソートしたデータを返すようにしています。
    return scan_response
```

```py:SamplePostFunc
import datetime
import boto3
from boto3.session import Session

def lambda_handler(event, context):
    region = "ap-northeast-1"
    session = Session(
        region_name=region
    )
    dynamodb = session.resource('dynamodb')

    table = dynamodb.Table('Sample')

    put_response = table.put_item(
        Item = {
                'timestamp': int(datetime.datetime.now().timestamp()),
                'content': event['content'],
        }
    )
    scan_response = table.scan()
    scan_response['Items'] = sorted(scan_response['Items'], key=lambda x:x['timestamp'], reverse=True)
    return scan_response
```

SamplePostFuncは入力データとして以下のフォーマットで送付されてくることを前提としています。

```json
{
    "content": "formで入力されたコメント"
}
```

各Lambda functionのトリガーとして、「API Gateway」を選択し、新しくAPI Gateway「SampleApi」を作っておきます。

# API GatewayでAPI定義

ここからはAPI Gateway側の設定に入ります。
先程作成したSampleApiのリソース設定を行います。

/v1/sampleapiという階層でリソースを作成します。
このリソースに対して、GETとPOSTのメソッドを作成。
各メソッド作成時のセットアップで、「統合タイプ」をLambda関数を選択し、先程作成したLambda functionをそれぞれ紐付けておきます。

![qiita_api_create2.png](https://qiita-image-store.s3.amazonaws.com/0/45851/5fd33c59-0521-9d2a-1e70-3d1fa5317723.png)

これでまずベースの定義は完了。

# API Gateway フローの設定(POSTデータの引き渡し加工&マッピングテンプレートで表示用のHTMLを生成)
ここからは、リクエストが来たあと、Lambdaにデータを引き渡す部分の設定と受信したデータ(JSON形式)をマッピングテンプレートを使ってHTMLに加工して戻すところの設定を行います。

## マッピングテンプレートを使ったHTML生成
まず、GETリクエストが来たときにLambda function(SampleGetFunc)からの戻りのJSONデータをHTMLに変換してみます。

先程作成したGETメソッドの設定の中で、「統合レスポンス」の項目を選択します。
この中で、「本文マッピングテンプレート」を設定します。
Lambda functionからの戻りのjsonデータが来た場合、ここのマッピングテンプレートに指定した形式に変換してレスポンスを返す設定です。

![qiita_api_get_create1.png](https://qiita-image-store.s3.amazonaws.com/0/45851/cfcf5197-0501-5dac-d575-12d95d070dc0.png)

Content-Typeをapplication/jsonとし、以下のようなHTMLを返すよう設定します。

```html
#set($inputRoot = $input.path('$'))
 
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script type="text/javascript">
        <!--javascriptのコード-->
        </script>
        <style type="text/css">
        <!--cssのコード-->
        </style>
    </head>
<body>
    <div class="base">
        <div class="title">運用課題を自由に投稿してください </div>

        <div class="caution">
            <span class=box-title>Caution</span>
            <p>注意書き</p>
        </div>
        <form method="POST" enctype="application/x-www-form-urlencoded" action="https://API URL/prod/v1/sampleapi">
            <textarea name="comment" placeholder="Type your issues...." rows=5></textarea>
            <input type=submit value="投稿">
        </form>

        <div class="title">最近投稿された運用課題</div>
        #foreach($elem in $inputRoot['Items'])
        <div class="issue">
            "$elem.content"
            <div class="timestamp">
                "$elem.timestamp"
            </div>
        </div>
        #end
    </div>
</body>
</html>
```
冒頭の1行で、Lambda functionから返されたjsonデータを取得しています。formの生成と、取得したjsonデータをforeachで取得件数分ループさせて、DynamoDBに登録されているtimestamp、contentの情報を出力させています。

さらに、以下の「メソッドレスポンス」の設定で、ユーザに応答を返す際のContent-Typeを定義します。HTMLと認識してほしいので「text/html」としておきます。

![qiita_api_get_create2.png](https://qiita-image-store.s3.amazonaws.com/0/45851/3723f2a4-bc8c-788d-ed4e-45725cfa97d3.png)

これでHTMLで返すための設定はOKです。

## POSTデータ処理する設定
次に、POSTリクエストが来た際にリクエストデータを処理するための設定を行います。
POSTメソッドの設定の「統合リクエスト」を選択して、「本文マッピングテンプレート」
からformで送付されてきたリクエストのContent-Type「application/x-www-form-urlencoded」を受信したときの受信データ処理の内容を記載します。

```plaintext
{
#set( $tmpstr = $input.body )
#foreach( $keyandvaluestr in $tmpstr.split( '&' ) )
#set( $keyandvaluearray = $keyandvaluestr.split( '=' ) )
        "$keyandvaluearray[0]" : "$keyandvaluearray[1]"
#end
}
```
この辺りの対応は[こちらの記事](https://dev.classmethod.jp/cloud/aws/sugano-013-api-gateway/)とか参考にさせてもらいました。

これで、POSTリクエストに含まれてくるデータをJSON形式に変換して、Lambdaに送付します。


設定は以上で完了です。
あとは、設定したAPIをdeployすれば実際に使えるようになります。

# まとめ
少し強引ですが、API GatewayとLambdaでアプリケーション部分は完結でき、データはDynamoDBに格納するといったことを試してみました。静的コンテンツをちゃんと管理するならS3に配置するとかやらないとマッピングテンプレートの管理は面倒です。
とはいえ、API Gateway便利に色々活用できますね。

いつ止めるかわからないですが、作成したアプリはここで動かしてます。
匿名で不満とかを吐き出す口としてちょっと用意してみたのでよければ運用管理上の課題感とかつぶやいてみてください。
http://prt.nu/0/opsshare




----