AWS初心者（一ヶ月）がサーバレス開発　～ API Gateway & Lambda & DynamoDB 編 ～


[前回](https://qiita.com/k-mishima/items/94a46d81951d0a4c3f3e)に引き続き、AWS を触り始めて一ヶ月強の人間がサーバレス開発します。

# 脳内作戦会議、再び

静的コンテンツの公開までこぎつけたわけですが、
AWS を触り始めて一ヶ月強の僕には荷が重いままなわけで。
シンプルすぎる仕様は減らせない荷物なわけで。
適当な作戦が思いつかぬまま、下手の考え休むに似たり、と感心する頃には半日経過しちゃってるわけで。

|![capture_000022.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/5cf19a63-7b64-8053-9374-51834f7ce531.png)|
|:--|

- 作業a : [S3 & CloudFront](https://qiita.com/k-mishima/items/94a46d81951d0a4c3f3e)
- 作業b : API Gateway & Lambda & DynamoDB (本記事)
- 作業c : CloudWatch と サービス間の接続周り 

# API Gateway & Lambda & DynamoDB

しっかり休んだところで、やりたいことはコレだ。

- DynamoDB にテーブル作成
- Lambda で DynamoDB にデータ投入
- API Gateway から Lambda を呼び出す

## DynamoDB

仕様を再確認する。

|![capture_000000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/724612f9-f726-5544-f970-1e6a2404e4e5.png)|
|:--|

氏名、メールアドレス、電話番号の３属性、キーはメールアドレスね。
ということで公式チュートリアルを横目に、

|![dynamo_create1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/9c68a44c-ea0d-b093-ecba-31b0bc12800c.png)|
|:--|

がんばれ、がんばれ

|![capture_000001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/37f5edef-0779-e18c-6593-bf1375bea3ec.png)|
|:--|

がんばれ、がんばれ

|![capture_000002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/649b1073-92a5-84e6-1d51-9b91090859a4.png)|
|:--|

ドカッと弁当がデカい男を登録できました。
満足。

## Lambda

初心に帰れと言わんばかりのメニューアイコンを選択しつつ、
一から自分で頑張って作成するぜ、と意気込みます。

|![lambda1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/1b1fee69-67c7-f646-1f41-da1627f7ca50.png)|
|:--|

ランタイムはデフォルトで選択されていた `Node.js` でいいや。
社内でも「わかりやすいと思うよー」とオススメされたし。

|![lambda2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/fc0e5ed9-180e-596e-cb45-f3f4ebfb8922.png)|
|:--|

デフォルトの実行ロールのままだと 
ログ出力以外に他のサービスとアレコレできないっぽい。

ということで IAM コンソールに寄り道してロールを作成。

|![lambda4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/60b76e73-3a9f-1b2e-2e55-379d15ffb423.png)|
|:--|

Lambda に戻ってロールを選択して

|![lambda5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/701d0402-6a2d-801c-3dd8-08a3a936cc70.png)|
|:--|

作成すると

|![lambda6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/80e90a41-7f02-332e-9c7d-7d6aa2791de6.png)|
|:--|

ひとまずできた、が、コレは枠組みでしかないわけで。

そして、Web 上に散らばる先人たちのコードをパッチワークするしかない僕なわけで。
一から自分で頑張ると意気込んだばかりだというのに。


```Node.js
const AWS = require('aws-sdk');
const documentClient = new AWS.DynamoDB.DocumentClient();
const util = require('util');
    
exports.handler = function(event, context, callback) {

    console.log(util.inspect(event, false, null));

    var input_email = (event.body.email) ? event.body.email : "*";
    var input_name  = (event.body.name)  ? event.body.name  : "*";
    var input_phone = (event.body.phone) ? event.body.phone : "*";

    var params = {
        TableName: 'masklottery_entries',
        Item: {
            'email': input_email,
            'name': input_name,
            'phone': input_phone
        },
        ConditionExpression: 'attribute_not_exists(email)'
    };

    var response = {
        "headers": {
            "Content-Type": "application/json",
        },
        "body": ""
    };

    documentClient.put(params, function(err, data) {
        if (err) {
            console.log("***** failure *****");
            console.log(err);
            response.body = JSON.stringify(err);
            callback(null, response);
        } else {
            console.log("***** success *****");
            console.log(data);
            response.body = JSON.stringify(data);
            callback(null, response);
        }
    });
};
```

先程作成した DynamoDB にデータを追加するコードを作成してみた。
気を使ったトコロは `ConditionExpression: 'attribute_not_exists(email)'` で、
意味は "キーがDB上に存在しない場合だけ(更新する)" という条件になるみたい。

|![capture_000000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/6e1d84e9-bdaf-09d0-5c4a-cb20145e58fc.png)|
|:--|

`同一メールアドレスでの申込は１回のみ` という仕様を取り入れてみました。
同一キーに対して DynamoDB は RDBMS で言う一意制約違反は発生せず上書き更新するらしいので。

テストデータもがんばって作成します。

|![capture_000018.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/cb39a048-b5c7-f0aa-e283-5752c1e8e8fa.png)|
|:--|

葉っぱ咥えたアイツはどうかな？

|![capture_000019.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/b363aa5f-99ed-a93c-0517-b88eed3a6003.png)|
|:--|

よしよし、登録できた。
再度実行してみると、

|![capture_000020.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/9de541ac-8b03-0d61-9125-9a984113420f.png)|
|:--|

うんうん、想定通りエラーになることを確認できた。
アレが２人もいたら迷惑ですよね。

DynamoDB でも確認してみる。

|![capture_000007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/50db8836-7235-09c8-058e-ae9c9c3a54ca.png)|
|:--|

葉っぱ咥えたアイツの２人目は見当たらない。
満足。


## API Gateway

「これからは HTTP API だ！」なノリで AWS 公式の説明が推してくるけど、
パラメータをアレヤコレヤできないので REST API を選択するわけで。

|![capture_000025.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/8cabe53c-5c93-ade1-2e02-19cae61a76d9.png)|
|:--|

付け加えると、API Gateway からの作成ではなく、Lambda からのトリガーの追加で作成です。
イイ感じに入力の手間が減るのです。

|![capture_000026.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/524ae16a-3a5b-394f-e64b-35ef810a2406.png)|
|:--|

メソッドが ANY だけど、DynamoDB に登録するだけの単一機能だし問題はないな。

次に統合リクエストから `Lambda プロキシ統合の使用` のチェックを外して

|![capture_000027.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/55ce362c-2130-e6fd-185f-6ba8814dc5d5.png)|
|:--|

パラメータマッピングを仕込む。

|![capture_000028.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/d8e0a71d-8fd8-c8b5-5c0e-dff2bcd8c592.png)|
|:--|

テンプレートのコードですか？
パッチワークですよ、一から自分で頑張りきれないですよ。
はい、デプロイ。

|<img width="400" alt="capture_000032.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/7e7fc723-7062-d2a8-438f-e61b3a1d24be.png">|
|:--|

これで HTML の POST でリクエストするパラメータを Lambda で受け取れることになったわけです。

テストは `POSTMAN`。
リクエストパラメータに語尾が「～ずら」のアノ人を仕込むずら。

|![capture_000030.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/acbc632b-e29c-d8a1-150a-3007c2db2e7a.png)|
|:--|

200 でかえってきたずら。

|![capture_000033.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/a988b33c-b2c2-f26f-d25f-6402ff7a5afd.png)|
|:--|

再度実行すると

|![capture_000035.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/d22e9457-a5f5-1092-6145-a8303b60ed68.png)|
|:--|

想定通りエラーになったずら。

念の為、DynamoDB も。

|![capture_000034.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1164835/7ee3edb5-3921-eb23-463f-105d78035f35.png)|
|:--|

語尾が「～ずら」のアノ人の２人目もいない。
満足。

ということで今回の目的は達した。

株式会社メソドロジック
三嶋　圭　@k-mishima

# 参考
[Class: AWS.DynamoDB.DocumentClient — AWS SDK for JavaScript](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html)
[Node.js による Lambda 関数のビルド - AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-nodejs.html)
[【DynamoDB】updateItemで新規項目を追加しないようにする 〜条件付き書き込み（ConditionExpression）を使って〜 - Qiita](https://qiita.com/jinto/items/5c31ef8d4cf0a1743b2d)
[AWS API GatewayでContent-Type:application/x-www-form-urlencoded のPOSTデータを受け取り JSONに変換する - Qiita](https://qiita.com/durosasaki/items/83af014aa85a0448770e)
[【AWS】API GatewayのMapping Templateで、Key=ValueペアをJSONに変換する - Qiita](https://qiita.com/tmiki/items/32654e85a925bb841f7d)