俺みたいになるな！Lambda+Node.jsでDynamoDBにputItemするだけで半日溶かしたAWS SAP

## やりたいこと
ただただシンプルにLambdaでDynamoDBに書き込みたい。それだけなんです。初体験でも無い。
なのにハマって、シルバーウィークの初日の半日それで溶かしてしまいました。
AWS SAPの称号を剥奪されるかと思って誰にも相談できませんでした。

https://qiita.com/kakudaisuke/items/174ff82b558b86d041b4

ちなみに、なんでそんなことしたいかというとコレです↓ :frog: 

https://qiita.com/kakudaisuke/items/18681418cebc3bee93b5

## 実装！
### DyanmoDBの設計
完了したタスク(task)を、ユーザー(user)、日付(date)ごとに登録したいです。
で、取り出す時はあるuserとdateのtaskを全て取得したい。

試行錯誤の末、保存したいアイテムはuser, unixtime, date, taskにしました。

ちなみに、user, dateはDynamoDBの予約語。予約語だけど使えちゃう。queryする時にちょい厄介だけど使えちゃう。

DynamoDBをこんな感じで用意します。

```plain
table_name: done_tasks

user:     String, パーティションキー 
unixtime: Number, ソートキー
date:     String, ローカルセカンダリインデックス
task:     String
```

本当はunixtimeは要らないのですが、今回使うputItemメソッドは、**プライマリキー（パーティションキー+ソートキー）が一緒だと上書きしてしまう**ので、userとdateが同じだと複数のtaskがDBに登録できなくなってしまいます。それでunixtimeをソートキーに採用しました。

ちなみに、RDBに慣れてると違和感あるかもですが、DynamoDBを新規作成する時に、taskの分の属性（カラム）は事前に作ってなくても大丈夫です。putItemする時にない属性は自動で作られます。（てか厳密に言うと、属性（attribute）って呼んでるぽいのでカラムではないのでしょう。）

### Lambdaの実行ロールにDynamoDBを触る権限を付与する
以前入社研修で作った社内IoTシステムには、`AmazonDynamoDBFullAccess`なんていう暴力的なポリシーを取り敢えず付けてしまっていたが、今回はプロらしく真面目にやる。

Lambda→設定→実行ロールからこのLambdaの実行ロールに飛んで以下のポリシーを作成してアタッチ。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:Scan",
                "dynamodb:Query"
            ],
            "Resource": "arn:aws:dynamodb:<region>:<account_id>:table/<table_name>"
        }
    ]
}
```

### DynamoDBにputItemする
このドキュメントを元に書く。

https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/dynamodb-example-table-read-write.html#dynamodb-example-table-read-write-writing-an-item

```javascript:index.js
const AWS = require('aws-sdk');

// これLambdaでは別にいらないぽい。書いても怒られないけど、やらなくてもsetされてる。
AWS.config.update({region: 'ap-northeast-1'});

// クライアントを生成する
// {apiVersion: '2012-08-10'}もLambdaでは別にいらないぽい。書いても怒られないけど、やらなくてもsetされてる。
const ddb = new AWS.DynamoDB({apiVersion: '2012-08-10'});

const user = "kakudaisuke";
const unixtime = 1663160618.493949;
const date = "2022/1/1";
const task = "仕様を固める";

// Lambdaの本ちゃんの処理始まる
exports.handler = async (event) => {
/*
  ごにょごにょ処理して...
  さらにごにょごにょ処理して...*/

  let params = {
    TableName: 'done_tasks',
    Item: {
      'user': { S: user },
      'unixtime': { N: unixtime },
      'date': { s: date },
      'task': { S: task }
    }
  };

  // awaitと.promise()がめっちゃ大事。
  const res = await ddb.putItem(params, function(err, data) {
    if (err) {
      console.log("Error", err);
    } else {
      console.log("Success", data);
    }
  }).promise();

  console.log("putItem終わったよ");

  return;
```

CloudWatch Logsを見ても、`console.log("Error", err);`が何も出力されていませんでした。でもputItemの後の`putItem終わったよ`のログは出力されている。
今までのAWS実装では、何度かロール、ポリシーで適切な権限を与えていないという原因で、実装をミスっていたことがありますが、今回はちゃんとしてる。ってめっちゃくちゃハマってました。DynamoDB ドキュメントクライアントにして、メソッドを`put`にみても結果同じ。

https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/dynamodb-example-document-client.html

なぜだ、なぜなんだ！
半日くらい溶けました。
というところで、過去につよつよ先輩エンジニアと一日会初合宿で実装したコードを眺めてたらヒントを得ました。

上のコードにあるように、putItemメソッドに`await`と`promise()`を入れてみたら成功しました！
というわけで上のコードは合ってるコードです。（実際のコードから簡略化して書いてるので、そのままコピペしても動かないかもです :pray: ）

つまり、非同期処理でputItemが時間かかってる間に`return;`してしまっているもんだから、DynamoDBに書き込もうとしている間にLambdaの処理が終わってしまったのだと思います。たぶん。だからputItemの中のログすら出力されていなかったんだと思います。たぶん。

## 最後に
ドキュメントクライアントってのもあって、Lambdaの最初用意されてたコードにもそちらで書かれてましたが、それだと型の指定とかしないで同じ処理を簡潔に書けるそうです。

ツッコミとかもっといい方法とかアドバイスとかあれば、ぜひコメントください:bow: