リアルタイムフィードバックシステムの作り方 with LINE Bot ~DynamoDB連携編~

Pocket

はじめに

前回は、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!」の文字が送られるたびにテーブルにレコードを登録して、そのタイムスタンプを使ってグラフ表示していました。コード全文は次の通りです。

実際に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

(2)AWS.DynamoDB.DocumentClient

おまけ

おまけで、Dynamoに登録したデータをLINE上に表示できるようにしてみます。特定の文字列(ここでは例として「一覧」)が来た場合に条件分岐して、「MISO_TABLE」内に登録されているユーザIDに紐づくテキストを全件取得して、一覧表示させてみます。「bravo!」では、ユーザがコメントを送信したい発表者を選ぶ際に、発表者の名前を一覧表示するのに使っていました。


 

さらにおまけ

さらにおまけで、カルーセルで表示したテキスト一覧のうち、選択したものをテーブルから削除するようにしてみます。一覧取得時に、プライマリキーであるタイムスタンプをポストバックのデータに仕込んでいなかったので、一度再検索を挟んでいます。「bravo!」では、削除ではなく、選択した発表者へのコメント送信に使用していました。

該当レコードが削除されていることが確認できます。

注意しておきたいこと

今回の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への負担を減らすことを心がけましょう。

お問い合わせ先

執筆者プロフィール

Yamauchi Kentaro
Yamauchi Kentarotdi IT技術推進部
社内の開発プロジェクトの技術支援や、Javaにおける社内標準フレームワークの開発を担当しています。Spring BootとTDDに手を出しつつ、LINE Botとかもいじったりしています。最近のマイトレンドはDevOps!
Pocket

関連記事