地味に大変な採番作業を、AWS API Gateway + Lambda＋DynamoDBで対応する

# はじめに
いろいろと連番を作る機会があると思います。

手元にDBサーバがあれば、あんまり苦労をしなくていいのですが、遠隔地だったり接続元のネットワークが別だったりするとちょっと手間になってしまいます。その手間をAWS API GatewayとLambdaとDynamoDBでどうにかしたいと思い、Node.jsの勉強をしつつ作ってみました。

ID単位で番号を発番できるので、「伝票番号を発番する」「その日の入場人数をカウントする」などにつかえて便利です。

# 準備

## DynamoDBにテーブルを作る。
- テーブル名：好きな名前をつけて下さい。ここでは、`numbering-public`というテーブル名にしました。
- プライマリーキー：`keyid`にしました。変更する場合は、コードの該当箇所も変更してください。

<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/51899328-3ff6-a4cf-b1ab-778e43e1fb24.png" width="75%">

テーブル作成後、しばらくするとテーブルの概要が確認できます。"Amazon リソースネーム（ARN）"`arn:aws:dynamodb:ap-northeast-1:999999999999999:table/numbering-public`をメモしておきましょう。後で使います。

## Lambdaを作る
- 名前：好きな名前をつけて下さい。ここでは、`numbering-qiita`という名前に。
- ランタイム：`node.js 6.10`
- ロール：`テンプレートから新しいロールを作成`
- ロール名：好きな名前をつけて下さい。ここでは、`lambda-numbering-qiita`という名前に。
- ポリシーテンプレート：`S3 オブジェクトの読み取り専用アクセス権限`を仮に設定しました。


<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/615cfc54-6be8-8ab1-61bc-4edca0e3563a.png" width="75%">

## ロール＆ポリシーの編集
いまのままではDynamoDBに接続できないので、ロール＆ポリシーを調整します。
1. さきほど作成したロール：`lambda-numbering-qiita`を開いて「インラインポリシーの追加」から追加していきます。
2. Policy Generatorを選択
3. Policy Generatorで必要事項を埋めて、「ステートメントを追加」

- 効果：`許可`
- AWS サービス：`Amazon DynamoDB`
- アクション：`すべてのアクション(*)` ※本当は最小限のアクションのみを付けたほうがよいです
- Amazon リソースネーム（ARN）：`arn:aws:dynamodb:ap-northeast-1:999999999999999:table/numbering-public` ← DynamoDBを作ったときにメモしたARNです

すべての入力が終わったら「ステートメントを追加」してください。その後「次のステップ」へ。

<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/8a9c67b2-f9ad-ee5f-4537-1659216d954a.png" width="75%">

ポリシーの確認画面が表示されるので、確認して、必要に応じて編集して「ポリシーの適用」

## AWS API GatewayでAPIを作る
- API名：好きな名前をつけて下さい。ここでは、`numbering-qiita`という名前に。
- 説明：後で見て判別/判断できる説明を書きましょう。
- エンドポイント：`エッジ最適化`。ごめんなさい、エッジ最適化と地域、どちらがいいかわかりません。

<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/95c7b7fc-2dc1-a3e9-6ea0-ab1e62b6e689.png" width="75%">

### アクションから「リソースの作成」をする

- プロキシリソースとして設定：チェックなし
- リソース名：`keyid`
- リソースパス：`{keyid}` ※中括弧`{}`でくくることでパラメータ変数として扱えます。
- API Gateway CORS を有効にする：チェックなし

<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/f9e12fd3-6104-303f-4f7c-6ab79ef7b03e.png" width="75%">

### {keyid} - GET - セットアップ
- 統合タイプ：`Lambda 関数`
- Lambda プロキシ統合の使用：`チェックあり`
- Lambda リージョン：Lambdaを作ったリージョンを指定してください
- Lambda 関数：`numbering-qiita`
- デフォルトタイムアウトの使用：`チェックあり`

<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/9d198280-9b3b-18ca-42a3-b78476e6fab7.png" width="75%">

## APIのデプロイ

- デプロイされるステージ：`[新しいステージ]`
- ステージ名：`v1`。好きな名前をつけて下さい。
- ステージの説明：分かりやすい説明を
- デプロイメントの説明：分かりやすい説明を

<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/09a18e5d-eb7a-c347-7f72-cc015fe8dd89.png" width="75%">

## APIのステージ
ステージ変数から「ステージ変数の追加」をします。

- 名前：`tablename`。DynamoDBのテーブル名を設定するためLambda関数内で使用しています。
- 値：`numbering-public`。DynamoDBのテーブル名。

<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/80cb0000-3536-0dff-ebca-0ff2d7adbadb.png" width="75%">

# Lambda関数を仕上げる
## 連携の確認
左側に「API Gateway」、右に「Amazon DynamoDB」が追加されていればOKです。
<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/1c2c9e3b-5fc7-2f64-eb47-5c88049c13d0.png" width="75%">

## ソースコード
```js:index.js
"use strict";

var AWS = require('aws-sdk');
var documentClient = new AWS.DynamoDB.DocumentClient();

function asyncPutDb(tablename, hashKey) {

 return new Promise(function(resolve, reject) {
  var params = {
   TableName: tablename,
   Item: {
    keyid: hashKey,
    countindex: 1
   }
  };

  documentClient.put(params, function(err, data) {
   if (err) {
    reject("asyncPutDb");
   }
   else {
    resolve(1);
   }
  });
 });
}

function asyncUpdateDb(tablename, hashKey) {

 return new Promise(function(resolve, reject) {
  var params = {
   TableName: tablename,
   Key: {
    "keyid": hashKey
   },
   UpdateExpression: "set countindex = countindex + :addvalue",
   ExpressionAttributeValues: {
    ":addvalue": 1
   },
   ReturnValues: 'UPDATED_NEW'
  };

  documentClient.update(params, function(err, data) {
   if (err) {
    resolve(-2);
   }
   else {
    resolve(data.Attributes.countindex);
   }
  });
 });
}

function setResultJson(hashKey, countindex) {
 return {
  keyid: hashKey,
  countindex: countindex
 };
}

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

 var response = {
  statusCode: 200,
  headers: { "Access-Control-Allow-Origin": "*" },
  body: {
   keyid: event.pathParameters.keyid,
   countindex: -1
  }
 };

 console.log("tablename:", event.stageVariables.tablename);
 console.log("keyid:", event.pathParameters.keyid);

 asyncUpdateDb(event.stageVariables.tablename, event.pathParameters.keyid)
  .then(function(resultUpdate) {
   if (resultUpdate > 0) {
    console.log("resultUpdate:", resultUpdate);
    response.body = JSON.stringify(setResultJson(event.pathParameters.keyid, resultUpdate));
    callback(null, response);
   }
   else {
    asyncPutDb(event.stageVariables.tablename, event.pathParameters.keyid, response)
     .then(function(resultPut) {
      console.log("resultPut:", resultPut);
      response.body = JSON.stringify(setResultJson(event.pathParameters.keyid, resultPut));
      callback(null, response);
     })
     .catch(function(err) {
      console.log("put error:", err);
      callback(null, JSON.stringify(err));
     });
   }
  });
};

```
保存を忘れずに。[最新版のコードはgistに掲載しています](https://gist.github.com/yyano/7f0c4239ac9ce2a87bef415bfacc72bb)。

## テスト用Json

```json:testcode
{
  "pathParameters": {
    "keyid": "abc1"
  },
  "stageVariables": {
    "tablename": "numbering-public"
  }
}
```
<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/53b810fb-f5d0-80e3-0624-21de0428683f.png" width="75%">

# 試してみる。
## Lambdaでのテスト
テスト用Jsonを登録後、「テスト」実行。

### 1回目
<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/bd6b2941-bf15-25e9-7e22-cfa67cc97cdf.png" width="75%">

### 2回目
<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/548b29ca-f6a4-ecd1-c6de-94ded5d41246.png" width="75%">

ちゃんと採番されてます。

## API Gatewayでのテスト
……テストしたいのですが、うまくステージ変数が反映されていないようでメソッドのテストができませんでした。

## ブラウザでのテスト
API Gatewayの作成したステージを選択後、上部に「URL の呼び出し」が表示されています。このURLをそのまま開くと`{"message":"Missing Authentication Token"}`エラーが出ますが、URLの後ろに`/qiita-test`などを追加すると動きます。

```diff
-https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/v1
+https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/v1/qiita-test
```

### 1回目
```json
{"keyid":"qiita-test","countindex":1}
```

### 2回目
```json
{"keyid":"qiita-test","countindex":2}
```

ちゃんと採番されてます。

# さいごに
Lambdaを128MBで0.1秒～2秒くらいで動くので、大抵は無料枠の中に収まると思います。