LambdaからDynamoDB操作

エヌシーアイ総合システムのmizu-ponです。

主に金融業務系システムの開発をしています。
AWSは勉強中ですが、知り得た知識などを載せていければと思います。

今回はLambdaからDynamoDBの操作(参照/更新)を行うまでの話です。

##**Lambdaって？**##
![AWS](https://s3-ap-northeast-1.amazonaws.com/mizusimt-s3/qiita_img/Compute_AWSLambda.png)

AWSが提供するサービスのひとつ。
ソースコード実行のためにサーバを立てる必要がなく色々と楽。(サーバレスアーキテクチャ)
幾つかの言語(Java,node.js(JavaScript),C#,Pythonなど)に対応している。

##**DynamoDBって？**##
![AWS](https://s3-ap-northeast-1.amazonaws.com/mizusimt-s3/qiita_img/Database_AmazonDynamoDB.png)

AWSが提供するNoSQLデータベース。
NoSQLは一般的に高速かつスケールアウトに強く、IoT/ビッグデータ向きといわれている。
データをキーとバリュー(値)の組み合わせで管理する(Key-Value型)

##**AWSコンソール画面**##
こんな感じです。リージョン(拠点)の指定や各種サービスの呼出しができます。

![AWS](https://s3-ap-northeast-1.amazonaws.com/mizusimt-s3/qiita_img/img016.jpg)

##**使用するテーブルの定義**##
今回はIoTセンサーデータをイメージした以下のテーブルを使用します。
DynamoDBのテーブルは、主キーとデータ部からなるシンプルな構成しか許容しません。
主キーにはパーティションキー(HASH)とソートキー(RANGE)を指定できます。
NoSQLなのでSQLでのデータ操作はできず(当たり前か…)、操作コマンド(メソッド)を使ってデータ操作します。

|Field|Type|Key|
|:-----:|:-----:|:-----:|
|datetime|String|HASH|
|sensor_mac|String|RANGE|
|info|JSON||

更に、infoには以下のようなデータが含まれます。

|gw_mac|rssi|fix|battery|status|reserve|user|fix2|reserve2|timestamp|exist|
|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|

##**開発環境(Cloud9)**##
開発環境にはAWSの統合開発環境サービスであるCloud9を利用します。

![Cloud9](https://s3-ap-northeast-1.amazonaws.com/mizusimt-s3/qiita_img/img017.jpg)

##**データの更新**##
更新用のソースコードは以下のリンクからダウンロードできます。ひとつずつ解説していきます。
Markdown: [JavaScript](https://s3-ap-northeast-1.amazonaws.com/mizusimt-s3/qiita_img/IotplatIOSingle_main.js "main.js")

初めの部分でrequireでライブラリ参照しています。
csvtojson：csv形式をjsonに変換
date-utils：日時のフォーマットを行う

```Javascript
console.log('Loading function');

const csv = require('csvtojson');
require('date-utils');
```

次にAWSやDynamoDBに接続するために`aws-sdk`を読み込んでいます。
`AWS.DynamoDB.DocumentClient`はDynamoDBのレコード操作を行うクラスです。

```Javascript
const AWS = require("aws-sdk");

AWS.config.update({
    region: "us-west-2"
});

const docClient = new AWS.DynamoDB.DocumentClient();
```
`exports.handler`はLambdaの関数定義の書式です。
`event`には実行時の引数が入ります。

```Javascript
exports.handler = (event, context, callback) => {

    console.log('readdata start.');

    let sensor_data = event.payload;
```
今回はセンサーデータとして以下の形式のデータを指定して渡します。

```
{
    "payload": "$GPRP,24718902B258,FE284D4457BC,-68,02010612FF0D0082BC350100FFFFFFFF000001000000"
}
```
次のコードでは、引数の形式からDynamoDBに格納する形式への変換を実施しています。

```Javascript
    // "$GPRP" is not need
    var sensor_data_non_gprp = sensor_data.replace(/^(\$GPRP,)/g, "");

    let payload = sensor_data_non_gprp.match(/^(.{12},.{12},.*,)(.*)/);
    let split = payload[2].match(/^(02010612FF0D0082BC)(.{4})(.{2})(.{8})(.{4})(.{2})(.{6})/); 

    // split payload.
    let fix = split[1];
    let battery = split[2];
    let status = split[3];
    let reserve = split[4];
    let user = split[5];
    let fix2 = split[6];
    let reserve2 = split[7];

    // make existFlg
    var existFlg = 0;
    if (status !== "00") {
        existFlg = 1;
    }

    let now = new Date();
    var sensor_data_split_payload = payload[1]
                        + fix + "," + battery + ","
                        + status + "," + reserve + ","
                        + user + "," + fix2+ ","
                        + reserve2 + "," + now.toFormat('YYYY-MM-DD_HH24:MI:SS.') + now.getTime() + ","
                        + existFlg;
```
さらに次のコードでcsv形式からDynamoDBに登録するJSON形式への変換を実施しています。

```Javascript
    csv({
        noheader: true,
        headers: [
            'sensor_mac',
            'gw_mac',
            'rssi',
            'fix',
            'battery',
            'status',
            'reserve',
            'user',
            'fix2',
            'reserve2',
            'timestamp',
            'exist'],
        colParser:{
            "exist":"number"
        }
    }).fromString(sensor_data_split_payload)
        .on('json',(jsonObj)=>{

        // Insert DynamoDB
        var params = {
            TableName:table,
            Item:{
                "datetime": jsonObj.timestamp,
                "sensor_mac": jsonObj.sensor_mac,
                "info":{
                    "gw_mac": jsonObj.gw_mac,
                    "rssi": jsonObj.rssi,
                    "fix": jsonObj.fix,
                    "battery": jsonObj.battery,
                    "status": jsonObj.status,
                    "reserve": jsonObj.reserve,
                    "user": jsonObj.user,
                    "fix2": jsonObj.fix2,
                    "reserve2": jsonObj.reserve2,
                    "timestamp": jsonObj.timestamp,
                    "exist": jsonObj.exist
                }
            }
        };
```

`docClient.put`メソッドで先ほど作成したparamsをDynamoDBへレコード挿入します。

```Javascript
        console.log("Adding a new item...");
        docClient.put(params, function(err, data) {

            if (err) {
                console.error("Unable to add item. Error JSON:", JSON.stringify(err, null, 2));
            } else {
                console.log("Added item:", JSON.stringify(data, null, 2));
            }
        });
```

**[コードの実行]**

Cloud9上で、"Run"を押下することでコードの実行ができます。

![Cloud9Exec](https://s3-ap-northeast-1.amazonaws.com/mizusimt-s3/qiita_img/img018.jpg)

##**データの参照**##
検索用のソースコードは以下のリンクからダウンロードできます。
Markdown: [JavaScript](https://s3-ap-northeast-1.amazonaws.com/mizusimt-s3/qiita_img/IotplatScanData_index.js "index.js")

引数に検索キーを指定し、`docClient.scan`メソッドで範囲検索します。

```
    var params = {
        TableName: table,
        FilterExpression: "#sm = :key1 AND begins_with(#dt, :key2)",
        ExpressionAttributeNames: {
            "#sm": "sensor_mac",
            "#dt": "datetime"
        },
        ExpressionAttributeValues: {
            ":key1": event.key1,
            ":key2": event.key2,
        }
    };

    console.log("Scanning Sensordata table.");
    docClient.scan(params, onScan);
```

**[コードの実行]**
Cloud9上の実行結果

![Cloud9Exec](https://s3-ap-northeast-1.amazonaws.com/mizusimt-s3/qiita_img/img019.jpg)

##**Lambda関数のデプロイ**##
Cloud9の左側に"AWS Resources"というメニューがあり、ここからデプロイします。

![Cloud9Deploy](https://s3-ap-northeast-1.amazonaws.com/mizusimt-s3/qiita_img/img020.jpg)

デプロイされると、AWS上にLambda関数として登録され、他から呼出すことが可能です。

![Cloud9Deploy](https://s3-ap-northeast-1.amazonaws.com/mizusimt-s3/qiita_img/img021.jpg)

以上、終わりです。