[AWS×Node.js] AWS Lambda(Node.js)で他のサービスと連携する(2018年版)

# はじめに

この記事は [AWS Advent Calendar 2018](https://qiita.com/advent-calendar/2018/aws) の7日目の記事です。
去年のアドベントカレンダーで「[AWSLambdaとその他のサービスを組み合わせていろんなことをしてみる](https://qiita.com/is_ryo/items/23604c06459872a88251)」という記事を書いたのですが、今年も1年間でLambdaのアップデートがあったので、2018年版という形で記事を書いていきます。

## AWSLambdaアップデートまとめ

今年あったLambdaのアップデートの中で個人的に印象が強かったものを上げてみます。

1. **Node.js v8.10** に対応
2. **Golang** に対応
3. 実行時間が **最大15分** に延長

といった感じです。
この他にもre:Inventで様々なアップデートがありましたね！
ランタイムにRubyを選択できるようになったり、CustomRuntimeで様々な言語をサポートしたり、ALB経由でinvokeできるようになったりと、まだまだLambdaは進化していくんだなと実感しました。

### 1. Node.js v8.10に対応

Node.jsユーザーの私からすると、Node.js8系への対応はかなり嬉しいものがありました。
なんと言っても `async/await` をLambdaでつ使えることで、非同期処理周りの書き方がとても楽になりましたね。
これについては「[AWS LambdaがNode.js8.10をサポートしたのでasync/awaitを試してみた](https://qiita.com/is_ryo/items/ae06ec278063296f4e2c)」でまとめてますので、よかったら見てあげてください。

今回の記事はNode.js v8.10で他のサービスと連携する内容を深掘りしていこうと思います。

### 2. Golangに対応

ちょっとGolangに興味があった時期にこのアップデートがあったので、試しに触ってみました。
「[AWSLambdaがGo言語に対応したのでDynamoDBと絡ませながらデモってみた](https://qiita.com/is_ryo/items/74ae077e66e3f1b68ca5)」でまとめてますので、よかったら見てあげてください。
DynamoDBに対して、GetItem / PutItem / Queryを実行しています。

### 3. 実行時間が最大15分に延長

今まで5分が最長だった実行時間が15分に延長されました。
これによって、より長くLambdaの中で処理を実行することができるようになったので、ちょっとしたバッチ処理などを実行させるのに有効かと思います。
ですが、個人的にはLambdaの中であまり長い処理を実行することはおすすめできません。

## 他のサービスと連携する

AWSLambdaを単体で利用される方はほとんどいないと思います。
LambdaからS3にテキストを保存したり、DynamoDBのデータを操作したり、SESやSNSのトリガーになったり……といろんなサービスと連携すると思います。
今回はS3 / DynamoDB / SES と連携する際のNode.jsの書き方をまとめていきます。

## S3

S3とはゴミ箱…ではなく、安価で高可用性なファイルストレージです。
主にテキストや画像データを保存するのに適しています。S3に保存しておくと、自動的に3箇所に複製されます。また、保存されているファイルに対してURLが付与されるので、取り出すのにもハードルが低いのが特徴です。

### ファイルを取得する(getObject)

```js
"use strict";
const AWS = require("aws-sdk");
const S3 = new AWS.S3();

exports.handler = async (event, context, callback) => {
  try {

    // パラメータ生成
    const getParams = {
      Bucket: "BucketName",
      Key: "objectKey.json"
    }

    // S3にGETするSDK(API)実行
    const obj = await S3.getObject(getParams).promise();

    return obj;
  } catch (err) {
    throw err;
  }
};
```


### ファイルを保存する(putObject)

```js
"use strict";
const AWS = require("aws-sdk");
const S3 = new AWS.S3();

exports.handler = async (event, context, callback) => {
   try {

    // JSON文字列作成
    const jsonFile = [{
      id: "12345",
      value: "1111"
    },{
      id: "67890",
      value: "2222"
    }];

    // パラメータ生成
    const purParams = {
      Bucket: "BucketName",
      Key: "sample.json",
      Body: JSON.stringify(jsonFile)
    }

    // S3にPUTするSDK(API)実行
    await S3.putObject(purParams).promise();

    return null;
  } catch (err) {
    throw err;
  }
};
```

## DynamoDB

DynamoDBはNoSQLのデータベースです。SQL文を生成してデータを操作するのではなく、API経由でデータの操作を行います。
今年はデータのバックアップ機能(PITR)やグローバルテーブルなどの大型アップデートがあったりと、最近Hotなサービスの一つです。

### 想定テーブル

テーブル名: sensorDataTable

| sensor_id | record_time | value |
|:------|:-----|:-----|
| a001 | 2018-12-07T00:00:00+09:00 | 10 |
| a001 | 2018-12-07T00:10:00+09:00 | 17 |
| a001 | 2018-12-07T00:20:00+09:00 | 20 |
| a001 | 2018-12-07T00:30:00+09:00 | 14 |
| a001 | 2018-12-07T00:40:00+09:00 | 10 |
| … | … | … |

### データを取得する(query)

```js
"use strict";
const AWS = require("aws-sdk");
const dynamoDB = new AWS.DynamoDB.DocumentClient(config);

exports.handler = async (event, context, callback) => {
  try {
    // パラメータ生成
    // テーブル sensorDataTable に対して sensor_id が a001 のデータを
    // record_time が 2018-12-07T00:00:00+09:00から昇順で100件取得する
    const params = {
      TableName: "sensorDataTable",
      KeyConditionExpression: "#hash_key = :hash_key AND #range_key >= :range_key",
      ExpressionAttributeNames: {
        "#hash_key": "sensor_id",
        "#range_key": "record_time" 
      },
      ExpressionAttributeValues: {
        ":hash_key": "a001",
        ":range_key": "2018-12-07T00:00:00+09:00"
      }, // 
      ScanIndexForward: true, // データの参照を昇順でするか、降順するか。 true: 昇順 /　false: 降順
      Limit: 100 // 取得するレコード数の上限
    };

    // DynamoDBからデータを取得するSDK(API)実行
    return await dynamoDB.query(params).promise();
  } catch (err) {
    throw err;
  }
}
```

### データを保存する(put)

```js
"use strict";
const AWS = require("aws-sdk");
const dynamoDB = new AWS.DynamoDB.DocumentClient(config);

exports.handler = async (event, context, callback) => {
  try {
    // パラメータ生成
    // テーブル sensorDataTable に対して sensor_id が a001 のデータを保存する
    const params = {
      TableName: "sensorDataTable"
      Item: {
        sensor_id: "a001",
        record_time: "2018-12-07T12:00:00+09:00",
        value: 25
      }
    };

    // DynamoDBからデータを保存するSDK(API)実行
    return await dynamoDB.put(params).promise();
  } catch (err) {
    throw err;
  }
}
```

## SES

SESはメールを送信してくれるサービスです。
S3やDynamoDBに比べると使用する場面は少ないと思いますが、私はよく利用しています。

```js
"use strict";
const AWS = require("aws-sdk");
const ses = new AWS.SES;

exports.handler = async (event, context, callback) => {
  try {
    // パラメータ生成
    // テーブル sensorDataTable に対して sensor_id が a001 のデータを保存する
    const params = {
      Source: "from_address@hoge.com",
      Destination: {
        ToAddresses: ["to_address@fuga.com"]
      },
      Message: {
        Subject: {
          Data: "メールのタイトル"
        },
        Body: {
          Text: {
            Data: "メールの本文"
          }
        }
      }
    };

    // メールを送信するSDK(API)実行
    return await ses.sendEmail(params).promise();
  } catch (err) {
    throw err;
  }
}
```

## まとめ

今回はLambdaからその他のサービスと連携するサンプルをまとめました。LambdaはAWSの他のサービスとの親和性がかなり高いサービスです。S3やDynamoDBなどのデータレイクに格納する前にちょっと加工したり、SESなどのトリガーになったりします。
AWSLambdaは直接invokeしようとするとクレデンシャルを持たせないといけないのですが、Lambdaの前段にAPIGatewayを挟むこととで、Lambdaをinvokeするためのクレデンシャルを持たずにinvokeすることができます。
ではまた！