Lambda+DynamoDB+API GatewayでRestAPI



Lambda単体とかEC2とかECSは普通に使っていたが、試そうと思っていたが出来ていなかったサーバレス関係のメモ。
[ここ](https://business.ntt-east.co.jp/content/cloudsolution/column-try-20.html)を主に参考にする。

ServerlesFrameworkを使えば楽なのかもしれないが、理解するためにもIaC関係は使わずに手で設定

# DynamoDB
AWSのコンソール画面でDynamoDBを開いて「テーブルの作成」を選択。
テーブル名：TestTable
パーティションキー：DeviceID
ソートキー：SensorID
で、残りはデフォルトで作成。

作成したテーブルに対して項目追加でレコードを作成すると同時にvalue, unitの項目作成。
適当にテスト用レコードを作成

# Lambda
LambdaからDynamoDBを操作できるようなIAMロールは必要。
AmazonDynamoDBFullAccess
AWSLambdaDynamoDBExecutionRole
をテストのためにアタッチしておいた。

PythonよりJavaScriptの方が慣れているのでnodejs（Node.js 14.x）で作成する。

DynamoをLambdaから扱う場合、AWS.DynamoDBとAWS.DynamoDB.DocumentClientがあるが、DocumentClientを使う方が楽

単純にキーで引っ掛けて検索

```javascript:sample
var params = {
    TableName: "TestTable",
    Key:{
         DeviceID: "Dev0001"
    }
};
docClient.get(params, function(err, data){
    if(err){
        console.log(err);
    }else{
        console.log(data.Item);
    }
});
```

追加

```javascript:sample
var params = {
    TableName: "TestTable",
    Item:{ // キーが必ず必要
         DeviceID: "Dev0003",
         SensorID: "Temp001",
         value: 30,
         unit: "c"
    }
};
docClient.put(params, callback);
```

削除

```javascript:sample
var params = {
    TableName: "TestTable",
    Key:{ // キー指定
         DeviceID: "Dev0003",
    }
};
docClient.delete(params, callback);
```

条件を複雑にしたクエリ

```javascript:sample
var params = {
    TableName: 'TestTable',
    ExpressionAttributeNames:{'#va': "value"}, // 予約語とか数値始まりは別名が必須
    KeyConditionExpression: '#va <= :val', // 条件指定
    ExpressionAttributeValues:{':val': 20} // 引数
};
// クエリの実行
docClient.query(params, function(err, data){
    if(err){
        console.log(err);
    }else{
       data.Items.forEach(function(devices, index){
           console.log(devices.DeviceID);
       });
    }
});
```
条件には
 * #x BETWEEN :min AND :max
 * begins_with (#x, :str)
 のようなものも存在する。

今回は単純なテストを行いたいだけなのでqueryを使った以下のようなものを作成

``` javascript:index.js
var AWS = require('aws-sdk');
var documentClient = new AWS.DynamoDB.DocumentClient();

var response = {
    statusCode: 200,
    headers: {},
    body: ""
  };

exports.handler =  async function(event, context) {
  // keys:[{"DeviceID":"xxx"},]
  // 内部的には複数返すようにしていない
  for (let key of event.keys) {
  
    var device_id = key.DeviceID;

    var param = {
        TableName : "TestTable",
        KeyConditionExpression: "#id = :val1",
        ExpressionAttributeNames:{ // 予約語の場合もあるので別名の割り当てが可能
            "#id": "DeviceID"
        },
        ExpressionAttributeValues: { // 引数。今回はソートキーは指定していない
            ":val1":device_id
        }
    };

    // awaitでpromiseが返ってくるのを待つ
    await documentClient.query( param, function( err, data ) {
        if (err) {
            response.statusCode = 500;
            response.body = err;
        } else {
            response.body = data;
        }
    } ).promise();

    return response;
  }
};
```

Lambda関数が用意できた。
LambdaにGET/PUTを渡すようにAPIGatewayで設定してやることも出来るが
メソッド単位にLambdaのスクリプトを作成するのが（処理速度も若干望めるので）一般的のようである。



 # API Gateway

このlambdaをCallするためのAPIGatewayを画面から設定する。
適当にDynamoTestというAPIを作成して、GETメソッドを追加。
（GETにする場合はスクリプト側のキーの取り方を変更）

 * メソッドリクエストの必須パラメータとしてクエリパラメータdevideを追加
 * 統合リクエストのapplication/jsonで以下を追加

```josn:マッピングテンプレート
{
  "keys": [{"DeviceID":"$input.params('device')"}]
}
```
（回りくどいやり方になってしまったが、テストになってのでよし）

テスト実行してみるとLambdaから返ってきた値が結果に表示された。
実際につかう場合はデプロイする事で反映される。
今回は自分用のテストなのでデプロイはしない。

APIキーを発行して[利用を制限](https://dev.classmethod.jp/articles/try-api-gateway-usage-plan/)する事も可能