はじめに
前回は、LINE BotとGrafanaを使ったリアルタイムフィードバックシステム「bravo!」の作り方について、クライアントサイドであるLINE Botの導入とその設定の仕方(LINE Botへメッセージを送ると、何かしら返事が返ってくるまで)をご説明しました。
そして今回は、botの実装部分である、LambdaとDynamoDBとの連携の仕方などを簡単にご紹介したいと思います。前回の記事はこちら↓。
※本ページに載せている画面は2018/10時点のもののため、設定画面のレイアウトなどが変更となっている可能性があります。
リアルタイムフィードバックシステム構成の概要
おさらいとなりますが、「bravo!」の構成は以下のようになっています。黒い四角内が前回設定した範囲、赤い四角内が今回実装する範囲です。
DynamoDBのテーブル作成
まずはLambdaから操作したいテーブルを作成します。
AWSコンソールから「DynamoDB」を選択し、ダッシュボードを開きます。その後、「テーブルの作成」ボタンをクリックします。
下記項目を入力し、ページ右下にある「作成」ボタンをクリックします。ここでは試しに、ユーザIDと日時をキーとしたテーブルを作ってみます。
ソートキーの利用などのテーブル構成については公式ドキュメント「DynamoDBのベストプラクティス」をご参照ください。
・テーブル名 : 任意
・プライマリキー : 任意
・ソートキーの追加 : 任意(プライマリキーを2カラムにしたい場合のみ入力)
ロールの設定
前回の記事ではDynamoDBにアクセスする処理を書かなかったため、ロールの設定を省きました。デフォルトのまま作成したロールでは、DynamoDBにアクセスする権限が付与されていないため、Lambdaの実行時に「AccessDeniedException」が発生してしまいます。そのため、DynamoDBにアクセスできるロールを設定する必要があります。
AWSのコンソールで「IAM」→「ロール」を選択し、Lambdaに適用されているロール名をクリックします。その後、「ポリシーをアタッチします」ボタンをクリックします。
※Lambdaに適用されているロールはLambdaのコンソール画面下部にある「実行ロール」から確認できます。
「ポリシーのフィルタ」に「DynamoDB」と入れると、DynamoDBに関連するポリシーが出てきます。その中で「AmazonDynamoDBFullAccess」にチェックを入れ、「ポリシーのアタッチ」ボタンをクリックします。
※要件に応じて適宜アタッチするポリシーを変えてください。
ロール名をクリックした後の画面になり、先程アタッチした「AmazonDynamoDBFullAccess」が表示されていることを確認します。
Lambdaの実装
では、Lambdaの実装をしていきましょう。まずは単純に、LINE Botを通じてユーザから送られてきたテキストをテーブルに登録してみます。LINE BotからのリクエストをトリガーにLambdaを発火させる設定は前回の記事をご参照ください。
「bravo!」では、ユーザから「bravo!」の文字が送られるたびにテーブルにレコードを登録して、そのタイムスタンプを使ってグラフ表示していました。コード全文は次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
// モジュール var request = require("request"); // requestモジュール var aws = require('aws-sdk'); // AWS SDKモジュール var docClient = new aws.DynamoDB.DocumentClient({region: 'ap-northeast-1'}); // DynamoDBのクライアント /** * 日付を文字列yyyy/MM/dd hh:mm:ssにフォーマットします。 */ var formatDateToString = function(targetDate) { var year = targetDate.getFullYear(); var month = ("00" + (targetDate.getMonth() + 1)).slice(-2); var date = ("00" + targetDate.getDate()).slice(-2); var hour = ("00" + targetDate.getHours()).slice(-2); var minute = ("00" + targetDate.getMinutes()).slice(-2); var second = ("00" + targetDate.getSeconds()).slice(-2); var result = year + "/" + month + "/" + date + " " + hour + ":" + minute + ":" + second; return result; }; /** * テキストメッセージを返します。 */ var sendReplyMessage = function(sendData) { // ヘッダー定義 // AuthorizationにはbotそれぞれのChannel Access Tokenを設定(LINE developersで確認できる) var headers = { "Content-Type": "application/json", "Authorization": "Bearer {ここにコピーしておいたChannel Access Tokenを書く}", }; // オプション定義 // 返事をするだけならbodyにSendDataを設定するだけで、他はいじらなくてOK var options = { url: "https://api.line.me/v2/bot/message/reply", // LINEbotの返事をしてくれるAPI headers: headers, // 上で設定したヘッダー json: true, body: sendData }; // ユーザへメッセージの送信 request.post(options, function(error, response, body) { // 送信成功の場合 if (!error && response.statusCode == 200) { console.log("Send reply succeeded.", body); // コンソールに何を送ったか確認出力 // 送信失敗の場合 } else { console.log("response : ", JSON.stringify(response)); // コンソールにエラーメッセージを確認出力 } }); }; /** * ユーザから送られてきたテキストを「MISO_TABLE」テーブルに登録します。 */ var insertText = function(userId, receivedText) { // テーブルに登録する情報 var params = { TableName : "MISO_TABLE", // テーブル名 Item : { USER_ID : userId, TIMESTAMP_NOW : formatDateToString(new Date()), TEXT : receivedText } // レコード情報 }; // テーブルに登録 docClient.put(params, function(err, data) { if (err) { // エラーログ console.error("Insert MISO_TABLE faild. Error : ", JSON.stringify(err, null, 2)); } else { // 完了ログ console.log("Insert MISO_TABLE successful.", data); } }); }; /** * LINEbotに何か送られてきたときのハンドラーです。 */ exports.handler = function(req, context) { var body = JSON.parse(req.body); // リクエストのbodyをJSONにする var replyToken = body["events"][0]["replyToken"]; // 返信する時に必要なトークン var userId = body["events"][0]["source"]["userId"]; // ユーザIDを取得 var sendData = null; // 送信データ // Message Event(テキスト)が送られてきたとき if(body["events"][0]["type"] == "message"){ // 送られてきたテキストメッセージを取得 var receivedText = body["events"][0]["message"]["text"]; // ユーザから送られてきたテキストを「MISO_TABLE」テーブルに登録 insertText(userId, receivedText); // 送信データ作成 sendData = { "replyToken": replyToken, // 返信する時に必要なトークン "messages": [{ // 送るメッセージ "type": "text", // 形式(今は「テキスト」) "text": "「" + receivedText + "」をMISO_TABLEに登録しました。" // 文字 }] }; // 返信メッセージを送信 sendReplyMessage(sendData); } }; |
実際にLINEからbotにメッセージを送信して、Dynamoのテーブルを見てみます。
MISO_TABLEに「MISO!」というテキストが登録されていることが確認できます。
※ここでは例として、ユーザIDにLINEのユーザIDをそのまま登録しています。LINEのユーザIDは他人に悪用されると勝手にメッセージの送受信が行われる危険性がありますので、取り扱いには十分注意してください。
「aws-sdk」モジュール
この記事の例ではLambdaにデフォルトで入っている「aws-sdk」モジュールを使用しています。AWS関連のサービスにアクセスするのであればこの方法が一番簡単だと思います。
「aws-sdk」モジュールを使ったやり方にはそれぞれ次の2つを使う方法があります。
(1)AWS.DynamoDB
(2)AWS.DynamoDB.DocumentClient
ほとんど同じなのですが、私としては②の方が好きです。というのも、②を使っていると自動でDynamoDB上の型に変換してくれるので、コードがシンプルになります。2つの違いをコード例で見てみましょう。
(1)AWS.DynamoDB
1 2 3 4 5 6 7 8 9 |
var dynamodb = new AWS.DynamoDB(); var params = { TableName: "TABLE_NAME", Item:{ "number" : { N : "10"}, // 整数型であることをNで指定 "text" : { S : "文字"} // 文字列型であることをSで指定 } }; dynamodb.putItem(params, function(data, error) {}; |
(2)AWS.DynamoDB.DocumentClient
1 2 3 4 5 6 7 8 9 10 |
var dynamodb = new AWS.DynamoDB(); var docClient = dynamodb.DocumentClient(); var params = { TableName: "TABLE_NAME", Item:{ "number" : 10, // そのまま指定 "text" : "文字" // そのまま指定 } }; dynamodb.putItem(params, function(data, error) {}; |
おまけ
おまけで、Dynamoに登録したデータをLINE上に表示できるようにしてみます。特定の文字列(ここでは例として「一覧」)が来た場合に条件分岐して、「MISO_TABLE」内に登録されているユーザIDに紐づくテキストを全件取得して、一覧表示させてみます。「bravo!」では、ユーザがコメントを送信したい発表者を選ぶ際に、発表者の名前を一覧表示するのに使っていました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
// モジュール var request = require("request"); // requestモジュール var aws = require('aws-sdk'); // AWS SDKモジュール var docClient = new aws.DynamoDB.DocumentClient({region: 'ap-northeast-1'}); // DynamoDBのクライアント /** * 日付を文字列yyyy/MM/dd hh:mm:ssにフォーマットします。 */ var formatDateToString = function(targetDate) { var year = targetDate.getFullYear(); var month = ("00" + (targetDate.getMonth() + 1)).slice(-2); var date = ("00" + targetDate.getDate()).slice(-2); var hour = ("00" + targetDate.getHours()).slice(-2); var minute = ("00" + targetDate.getMinutes()).slice(-2); var second = ("00" + targetDate.getSeconds()).slice(-2); var result = year + "/" + month + "/" + date + " " + hour + ":" + minute + ":" + second; return result; }; /** * テキストメッセージを返します。 */ var sendReplyMessage = function(sendData) { // ヘッダー定義 // AuthorizationにはbotそれぞれのChannel Access Tokenを設定(LINE developersで確認できる) var headers = { "Content-Type": "application/json", "Authorization": "Bearer {ここにコピーしておいたChannel Access Tokenを書く}", }; // オプション定義 // 返事をするだけならbodyにSendDataを設定するだけで、他はいじらなくてOK var options = { url: "https://api.line.me/v2/bot/message/reply", // LINEbotの返事をしてくれるAPI headers: headers, // 上で設定したヘッダー json: true, body: sendData }; // ユーザへメッセージの送信 request.post(options, function(error, response, body) { // 送信成功の場合 if (!error && response.statusCode == 200) { console.log("Send reply succeeded.", body); // コンソールに何を送ったか確認出力 // 送信失敗の場合 } else { console.log("response : ", JSON.stringify(response)); // コンソールにエラーメッセージを確認出力 } }); }; /** * ユーザから送られてきたテキストを「MISO_TABLE」テーブルに登録します。 */ var insertText = function(userId, receivedText) { // テーブルに登録する情報 var params = { TableName : "MISO_TABLE", // テーブル名 Item : { USER_ID : userId, TIMESTAMP_NOW : formatDateToString(new Date()), TEXT : receivedText } // レコード情報 }; // テーブルに登録 docClient.put(params, function(err, data) { if (err) { // エラーログ console.error("Insert MISO_TABLE faild. Error : ", JSON.stringify(err, null, 2)); } else { // 完了ログ console.log("Insert MISO_TABLE successful.", data); } }); }; /** * 「MISO_TABLE」テーブル内のユーザIDに紐づくテキストを全件取得します。 */ var selectTextByUserId = function(userId) { return new Promise(function(resolve, reject) { // テーブルに登録する情報 var params = { TableName : "MISO_TABLE", // テーブル名 FilterExpression: "USER_ID = :USER_ID", ExpressionAttributeValues: { ":USER_ID": userId } }; // テーブルから検索 docClient.scan(params, function(err, data) { if (err) { // エラーログ console.error("Select MISO_TABLE faild. Error : ", JSON.stringify(err, null, 2)); } else { // 完了ログ console.log("Select MISO_TABLE successful.", data); // テキストの配列を作成 var texts = data.Items.map((item) => item.TEXT) .filter(function(x, i, self) { return self.indexOf(x) === i; }); resolve(texts); } }); }); }; /** * カルーセル用のカラムを作成します。 */ var createCarouselColumns = function(texts) { var columns = []; var actions = []; texts.forEach(function(text, index, texts) { actions.push({ "type": "postback", "label": text, "data": text }); }); columns.push({ "text" : "テキスト一覧", "actions" : actions }); return columns; }; /** * LINEbotに何か送られてきたときのハンドラーです。 */ exports.handler = function(req, context) { var body = JSON.parse(req.body); // リクエストのbodyをJSONにする var replyToken = body["events"][0]["replyToken"]; // 返信する時に必要なトークン var userId = body["events"][0]["source"]["userId"]; // ユーザIDを取得 var sendData = null; // 送信データ // Message Event(テキスト)が送られてきたとき if(body["events"][0]["type"] == "message"){ // 送られてきたテキストメッセージを取得 var receivedText = body["events"][0]["message"]["text"]; if (receivedText == "一覧") { // 「MISO_TABLE」テーブル内のユーザIDに紐づくテキストを全件取得 selectTextByUserId(userId).then(texts => { // カルーセル用のコンテンツを作成 var columns = createCarouselColumns(texts); sendData = { "replyToken" : replyToken, "messages" : [{ "type" : "template", "altText" : "一覧", "template" : { "type" : "carousel", "columns" : columns } }] }; // 返信メッセージを送信 sendReplyMessage(sendData); }); } else { // ユーザから送られてきたテキストを「MISO_TABLE」テーブルに登録 insertText(userId, receivedText); // 送信データ作成 sendData = { "replyToken": replyToken, // 返信する時に必要なトークン "messages": [{ // 送るメッセージ "type": "text", // 形式(今は「テキスト」) "text": "「" + receivedText + "」をMISO_TABLEに登録しました。" // 文字 }] }; // 返信メッセージを送信 sendReplyMessage(sendData); } } }; |
さらにおまけ
さらにおまけで、カルーセルで表示したテキスト一覧のうち、選択したものをテーブルから削除するようにしてみます。一覧取得時に、プライマリキーであるタイムスタンプをポストバックのデータに仕込んでいなかったので、一度再検索を挟んでいます。「bravo!」では、削除ではなく、選択した発表者へのコメント送信に使用していました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
// モジュール var request = require("request"); // requestモジュール var aws = require('aws-sdk'); // AWS SDKモジュール var docClient = new aws.DynamoDB.DocumentClient({region: 'ap-northeast-1'}); // DynamoDBのクライアント /** * 日付を文字列yyyy/MM/dd hh:mm:ssにフォーマットします。 */ var formatDateToString = function(targetDate) { var year = targetDate.getFullYear(); var month = ("00" + (targetDate.getMonth() + 1)).slice(-2); var date = ("00" + targetDate.getDate()).slice(-2); var hour = ("00" + targetDate.getHours()).slice(-2); var minute = ("00" + targetDate.getMinutes()).slice(-2); var second = ("00" + targetDate.getSeconds()).slice(-2); var result = year + "/" + month + "/" + date + " " + hour + ":" + minute + ":" + second; return result; }; /** * テキストメッセージを返します。 */ var sendReplyMessage = function(sendData) { // ヘッダー定義 // AuthorizationにはbotそれぞれのChannel Access Tokenを設定(LINE developersで確認できる) var headers = { "Content-Type": "application/json", "Authorization": "Bearer {ここにコピーしておいたChannel Access Tokenを書く}", }; // オプション定義 // 返事をするだけならbodyにSendDataを設定するだけで、他はいじらなくてOK var options = { url: "https://api.line.me/v2/bot/message/reply", // LINEbotの返事をしてくれるAPI headers: headers, // 上で設定したヘッダー json: true, body: sendData }; // ユーザへメッセージの送信 request.post(options, function(error, response, body) { // 送信成功の場合 if (!error && response.statusCode == 200) { console.log("Send reply succeeded.", body); // コンソールに何を送ったか確認出力 // 送信失敗の場合 } else { console.log("response : ", JSON.stringify(response)); // コンソールにエラーメッセージを確認出力 } }); }; /** * ユーザから送られてきたテキストを「MISO_TABLE」テーブルに登録します。 */ var insertText = function(userId, receivedText) { // テーブルに登録する情報 var params = { TableName : "MISO_TABLE", // テーブル名 Item : { USER_ID : userId, TIMESTAMP_NOW : formatDateToString(new Date()), TEXT : receivedText } // レコード情報 }; // テーブルに登録 docClient.put(params, function(err, data) { if (err) { // エラーログ console.error("Insert MISO_TABLE faild. Error : ", JSON.stringify(err, null, 2)); } else { // 完了ログ console.log("Insert MISO_TABLE successful.", data); } }); }; /** * 「MISO_TABLE」テーブル内のユーザIDに紐づくテキストを全件取得します。 */ var selectTextByUserId = function(userId) { return new Promise(function(resolve, reject) { // テーブルに登録する情報 var params = { TableName : "MISO_TABLE", // テーブル名 FilterExpression: "USER_ID = :USER_ID", ExpressionAttributeValues: { ":USER_ID": userId } }; // テーブルから検索 docClient.scan(params, function(err, data) { if (err) { // エラーログ console.error("Select MISO_TABLE faild. Error : ", JSON.stringify(err, null, 2)); } else { // 完了ログ console.log("Select MISO_TABLE successful.", data); // テキストの配列を作成 var texts = data.Items.map((item) => item.TEXT) .filter(function(x, i, self) { return self.indexOf(x) === i; }); resolve(texts); } }); }); }; /** * カルーセル用のカラムを作成します。 */ var createCarouselColumns = function(texts) { var columns = []; var actions = []; texts.forEach(function(text, index, texts) { actions.push({ "type": "postback", "label": text, "data": text }); }); columns.push({ "text" : "テキスト一覧", "actions" : actions }); return columns; }; /** * 選択されたテキストをテーブルから削除します。 */ var deleteTextByUserId = function(text, userId) { // テーブルに登録する情報 var params = { TableName : "MISO_TABLE", // テーブル名 FilterExpression: "USER_ID = :USER_ID AND #TEXT = :TEXT", ExpressionAttributeNames : { "#TEXT" : "TEXT", }, ExpressionAttributeValues: { ":USER_ID" : userId, ":TEXT" : text } }; // テーブルから検索 docClient.scan(params, function(err, data) { if (err) { // エラーログ console.error("Select MISO_TABLE faild. Error : ", JSON.stringify(err, null, 2)); } else { // 完了ログ console.log("Select MISO_TABLE successful.", data); // テキストの配列を作成 var targetRecord = data.Items[0]; params = { TableName : "MISO_TABLE", Key : { "TIMESTAMP_NOW" : targetRecord.TIMESTAMP_NOW, "USER_ID" : targetRecord.USER_ID } }; docClient.delete(params, function(err, data) { if (err) { console.error("Delete MISO_TABLE faild. Error : ", JSON.stringify(err, null, 2)); } else { console.log("Delete MISO_TABLE successful.", data); } }); } }); }; /** * LINEbotに何か送られてきたときのハンドラーです。 */ exports.handler = function(req, context) { var body = JSON.parse(req.body); // リクエストのbodyをJSONにする var replyToken = body["events"][0]["replyToken"]; // 返信する時に必要なトークン var userId = body["events"][0]["source"]["userId"]; // ユーザIDを取得 var sendData = null; // 送信データ // Message Event(テキスト)が送られてきたとき if(body["events"][0]["type"] == "message"){ // 送られてきたテキストメッセージを取得 var receivedText = body["events"][0]["message"]["text"]; if (receivedText == "一覧") { // 「MISO_TABLE」テーブル内のユーザIDに紐づくテキストを全件取得 selectTextByUserId(userId).then(texts => { // カルーセル用のコンテンツを作成 var columns = createCarouselColumns(texts); sendData = { "replyToken" : replyToken, "messages" : [{ "type" : "template", "altText" : "一覧", "template" : { "type" : "carousel", "columns" : columns } }] }; // 返信メッセージを送信 sendReplyMessage(sendData); }); } else { // ユーザから送られてきたテキストを「MISO_TABLE」テーブルに登録 insertText(userId, receivedText); // 送信データ作成 sendData = { "replyToken": replyToken, // 返信する時に必要なトークン "messages": [{ // 送るメッセージ "type": "text", // 形式(今は「テキスト」) "text": "「" + receivedText + "」をMISO_TABLEに登録しました。" // 文字 }] }; // 返信メッセージを送信 sendReplyMessage(sendData); } } else if (body["events"][0]["type"] == "postback") { var postbackData = body['events'][0]['postback']['data']; // ポストバックアクションのデータ // 選択されたテキストをテーブルから削除 deleteTextByUserId(postbackData, userId); // 送信データ作成 sendData = { 'replyToken':replyToken, // 宛先みたいなもの "messages":[{ // 送るメッセージ "type":"text", // 形式(今は「テキスト」) "text": "「" + postbackData + "」をMISO_TABLEから削除しました。" // 文字 }] }; // 返信メッセージを送信 sendReplyMessage(sendData); } }; |
該当レコードが削除されていることが確認できます。
注意しておきたいこと
今回のLambdaの実装で注意しておきたいことは以下の2点です。
1、非同期実行であるということ
今回使用している言語であるNode.jsは基本的に非同期で処理が実行されます。そのため、一覧の取得時などにデータ数が多いとレスポンス用のカルーセルが出来上がる前に送信されてしまうこともあるかもしれません。その場合は、Promiseなどの同期を行うなり、setTimeoutでメッセージ送信を待たせるなどする必要があるかと思います。
2、テーブルのカラム名に予約語を使用していること
今回お試しで作成したテーブルに「TEXT」というカラム名でデータを登録しました。しかし、これは予約語で、普通に「FilterExpression」の中で使用するとエラーになります。そこで、「ExpressionAttributeNames」でカラム名を指定することによって、予約語でも指定できるようになります。
おわりに
以上で、DynamoDB連携編は終わりです。LambdaとDynamoDBは非常に相性がよく、「aws-sdk」モジュールがデフォルトで入っているため、実装も比較的簡単だと思います。ただ、Lambda+Node.jsで実装すると基本的に非同期処理になりますよね。それを踏まえて実装していく必要があると、この「bravo!」を開発していて改めて感じました。ポイントは、Lambda+Node.jsの実装においては、シンプルな単機能で実装するということです。
非同期処理が基本ということに加えて、DynamoDBにはトランザクションの概念がなく、コミットやロールバックがないそうです。そのため、テーブル更新を複数回行うといった時間がかかる複雑な処理だと、前の処理が完了する前に次の処理がスタートするので、先行している処理に影響が出る可能性があります。そのためLambdaの処理は、あまり時間をかけない、シンプルな単機能である必要があります。
「bravo!」でも当初、1つのLambdaで複数テーブルを更新を行う構成にしており、多数のユーザから同時にリクエストが送信されるとデータがいくつかロストしてしまうということがありました。どうしても複数テーブルの更新が必要な場合は、DynamoDBの更新をトリガーにして別のLambdaを発火させるようにするとLambdaを単機能で分けやすくなるかと思います。APIだと考えれば当然のことなのですが、Lambdaの関数は最小単位で分けて、できる限り1つ1つのLambdaへの負担を減らすことを心がけましょう。
執筆者プロフィール

- tdi デジタルイノベーション技術部
- 社内の開発プロジェクトの技術支援や、Javaにおける社内標準フレームワークの開発を担当しています。Spring BootとTDDに手を出しつつ、LINE Botとかもいじったりしています。最近はマイクロサービスを勉強しつつ、クラウドアプリケーションを開発できるエンジニアの育成にも力を入れてます!