やりたいこと
ただただシンプルにLambdaでDynamoDBに書き込みたい。それだけなんです。初体験でも無い。
なのにハマって、シルバーウィークの初日の半日それで溶かしてしまいました。
AWS SAPの称号を剥奪されるかと思って誰にも相談できませんでした。
ちなみに、なんでそんなことしたいかというとコレです↓
実装!
DyanmoDBの設計
完了したタスク(task)を、ユーザー(user)、日付(date)ごとに登録したいです。
で、取り出す時はあるuserとdateのtaskを全て取得したい。
試行錯誤の末、保存したいアイテムはuser, unixtime, date, taskにしました。
ちなみに、user, dateはDynamoDBの予約語。予約語だけど使えちゃう。queryする時にちょい厄介だけど使えちゃう。
DynamoDBをこんな感じで用意します。
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の実行ロールに飛んで以下のポリシーを作成してアタッチ。
{
"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する
このドキュメントを元に書く。
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
にみても結果同じ。
なぜだ、なぜなんだ!
半日くらい溶けました。
というところで、過去につよつよ先輩エンジニアと一日会初合宿で実装したコードを眺めてたらヒントを得ました。
上のコードにあるように、putItemメソッドにawait
とpromise()
を入れてみたら成功しました!
というわけで上のコードは合ってるコードです。(実際のコードから簡略化して書いてるので、そのままコピペしても動かないかもです )
つまり、非同期処理でputItemが時間かかってる間にreturn;
してしまっているもんだから、DynamoDBに書き込もうとしている間にLambdaの処理が終わってしまったのだと思います。たぶん。だからputItemの中のログすら出力されていなかったんだと思います。たぶん。
最後に
ドキュメントクライアントってのもあって、Lambdaの最初用意されてたコードにもそちらで書かれてましたが、それだと型の指定とかしないで同じ処理を簡潔に書けるそうです。
ツッコミとかもっといい方法とかアドバイスとかあれば、ぜひコメントください