lambda入門(Node)③ - API Gatewayを使ってslackからのリクエストをlambdaで受けられるようにする

第3回になりました。

過去のはこちら。

- [lambda入門(Node) - serverless frameworkを使ってみる](http://qiita.com/smith_30/items/ae32c3596b86c49d40c2)
- [lambda入門(Node)② - lambdaからdynamoDBへデータを保存する](http://qiita.com/smith_30/items/b73290ca7410b59eccc1)

今回は、ようやくやりたいことに近づいて来まして
slackからのリクエストをlambdaで受けられるようにしたいと思います。

何か調べていくと、どうもAPI Gatewayを使うと良い感じぽい。
まずはAPI Gatewayについて予習を。

## API Gateway

### どんな役割をしてくれるのか

APIのエンドポイントとして待ち構える玄関として使える
現在は`hoge/*`のようなパスを`/hoge/{proxy+}`として設定できる
プロキシリソースなるものらしい
post, putの振り分けなどが簡単になった

### 課金体系

受信した API 呼び出しと、送出したデータ量に対して発生

## serverless fｒameworkでの設定

```yml:serverless.yml
functions:
  bookStore:
    handler: books/store.store
    events:
      - http:
          path: books
          method: get
          cors: true
```

これだけ。
デプロイ実行すると

```bash
$ serverless deploy -v --stage dev
Serverless: Packaging service...
・
・
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::Deployment - ApiGatewayDeployment1490662361327
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::Deployment - ApiGatewayDeployment1490662361327
CloudFormation - CREATE_COMPLETE - AWS::ApiGateway::Deployment - ApiGatewayDeployment1490662361327
・
・
Service Information
service: testProject
stage: dev
region: ap-northeast-1
api keys:
  None
endpoints:
  GET - https://xxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/books
functions:
  bookStore: testProject-dev-bookStore
```

エンドポイントが作られた。。！

レスポンスを送れるか試してみる

```bash
$ curl https://xxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/books
{"message":"ok",...........}
```

うん、okそう、すごい。ymlに書くだけで何でも設定してくれちゃう。

## Slackから応答させるようにする

ここで今回の本題。
作成したエンドポイントに対してスラックがリクエストを送って
slack上にメッセージが返ってくるようにする。

__全体像__

![スクリーンショット 2017-04-29 15.17.57.png](https://qiita-image-store.s3.amazonaws.com/0/47896/e287317f-b54b-b1bb-b9b4-f8259a9600de.png)

やることとしては、リクエストパラメータを解析してデータを保存。
保存した結果を返す

```js:book.js

'use strict';

const slackAuthorizer = require('../authorizer/slackAuthorizer');
const parser = require('../service/queryParser');

const bookSave = require('../useCase/book/save.js');

module.exports.book = (event, context, callback) => {
  const queryParser = new parser(event.body);
  const authorizer = new slackAuthorizer(queryParser.parseToken());
  
  /**
   * 認証
   */
  if (!authorizer.authorize()) {
    context.done('Unauthorized');
  }
  
  /**
   * @Todo ここで lambda function の振り分けを行いたい
   */
  bookSave(event, (error, result) => {});
  
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'ok',
    }),
  };
  
  callback(null, response);
};

```

同期で処理を行いたいので `async`を使って処理をブロックごとに実行する


```js:save.js

'use strict';

const AWS = require('aws-sdk');
const dynamoDB = new AWS.DynamoDB.DocumentClient();
const bookTable = process.env.bookTable;

const uuidV1 = require('uuid/v1');

const webhookUrl = process.env.slack_webhook_url;
const request = require('request');

const dateTime = require('node-datetime');
const dt = dateTime.create();
const insertDate = dt.format('Y-m-d H:M:S');

const async = require('async');

const parser = require('../../service/queryParser');

module.exports = (event, callback) => {
  
  const queryParser = new parser(event.body);
  
  const key = uuidV1();
  
  async.series([
    function(callback) {

      /**
       * データを保存
       */
      dynamoDB.put({
        'TableName': bookTable,
        'Item': {
          'id': key,
          'title': queryParser.parseText(),
          'insert_date': insertDate,
        },
      }, function(err, data) {
        callback(null, "saved");
      });
    },
    function(callback) {
      
      /**
       * 保存したものを取り出して
       * 結果を返す
       */
      dynamoDB.get({
        TableName: bookTable,
        Key: {
          id: key,
        },
      }, function(err, data) {
        if (!err) {
          const response = {
            text: `\`${data.Item.title}\` is Saved !!`,
          };
      　　
          /**
           * webhook でチャンネルにメッセージを返す
           */
          request.post(webhookUrl, {
            form: {
              payload: JSON.stringify(response),
            },
          }, (err, response, body) => {
            callback(null, 'getData');
          });
        }
      });
    },
  ], function(err, results) {
    if (err) {
      throw err;
    }
  });
};
```

slash commandから送られてきたパラメータをパースするために
`query-string`モジュールを使い、ラップした

```js:queryParser.js
'use strict';

const queryStringParser = require('query-string');

module.exports = class queryParser {
  
  constructor (queryString) {
    this.queryString = queryString;
  }
  
  parseToken () {
    return queryStringParser.parse(this.queryString).token;
  }
  
  parseText () {
    return queryStringParser.parse(this.queryString).text;
  }
};
```

slashコマンドの設定は省略します。
最初の api gatewayで設定されたエンドポイントのURLを
設定してあげればいいので

実際に動かすとこんな感じです。

![4b4b9687608f58545ef2b4536a7166c2.gif](https://qiita-image-store.s3.amazonaws.com/0/47896/94be2f11-e0e0-70a8-6e8d-af6ce010db01.gif)

ソースは汚いかもしれませんが、saverlessフレームワークで
エンドポイント作成やdb、外部サービスの連携が簡単に出来ました

もっとキレイに書けるようにjsの筋力をつけていかねば。。笑