AWSとReact.js+Fluxでつくる、サーバーレスなWEBアプリケーションサンプル（前編：AWS構築）

#概要

フロントエンド側を React.js + Flux で、サーバー側をAWSのマネージドサービスでサーバーレスな構成で簡単なWEBアプリケーションを作成します。

####概要イメージ
![概要.png](https://qiita-image-store.s3.amazonaws.com/0/141818/3af0bc04-5b38-42a6-1761-95043039c85b.png)

フロントエンド側はWEBページを使ってブラウザ上でUIを実現し、データ自体はHTTP通信を使ってサーバー側に保存するようにします。
従来の方法だとサーバー側にLAMP環境（Linux Apache Mysql PHP）を用意してRESTfulなサーバーを準備する形になります。

####従来的な作り方例

![従来構成.png](https://qiita-image-store.s3.amazonaws.com/0/141818/44732994-3c67-cafb-5872-08963f2c6141.png)


####サーバーレスなシステム構成

![サーバレス構成.png](https://qiita-image-store.s3.amazonaws.com/0/141818/dbb3cade-45e4-c41e-2a9b-0ae05fb91982.png)

サーバレスの方が複雑に見えるのは気のせいです。（設定要素をあれこれを書き込んだので複雑に見えるだけです。）
LAMP環境を構築する場合は、OSの設定からミドルウエア（Apache、PHP、MySQL）のインストールを行った後に、DBのテーブル作成から、PHPのスクリプト設定までやることは盛りだくさんとなりますが、AWSのマネージドサービスを使うと比較的簡単にこの環境を準備することができます。


##実装実例（AWS側）

それでは、AWSのマネージドサービスを利用してクライアントからHTTPリクエストを処理するRESTサービスを構築していきます。
※今回は全て東京リージョンで作成します。

###DynamoDBの作成

はじめにデータを保持するためにDynamoDBを作成していきます。LAMP環境だとMySQLに相当する部分ですね。
MySQLはリレーショナルなRDBですがDynamoDBはキーバリュー型のDBとなります。違いに関する詳細は割愛します。詳しくはAWSのドキュメントを調べてみてください。

今回は単純にふむふむボタンが押された記録を保存するだけのテーブルを作成します。
AWSコンソールのAWSサービスから「DynamoDB」を選択して「テーブル作成」ボタンを押して、以下のように必要な項目を入力してください。
![スクリーンショット 2016-09-27 14.41.31.png](https://qiita-image-store.s3.amazonaws.com/0/141818/366dd237-d8bb-c617-4607-41eed12d0a13.png)

| 項目 | 設定値 |
|:-:|:-:|
| テーブル名 | humuhumus |
| パーテーションキー | humuId |
| ソートキー | groupId |
ソートキーは必須では無いですが、今回はグループIDでグループ分けした投票数を集計したいのでgroupIdのソートキーを追加しています。

以上でDynamoDBの設定は完了です。

###IAMロール作成

次にアクセス権限の設定を行っていきます。今回はLambdaからDynamoDBにアクセスするための権限を作成します。AWSのマネージドサービスはそれぞれが独立して動作しているため、それぞれサービスを利用するために接続する際に操作する権限を取っておく必要があるためです。
※このIAMロールがAWSを利用するのに一番引っかかる点なのでよく理解することをおすすめします。

以下、Lambda関数から先ほど作成したDynamoDBの「humuhumus」テーブルにアクセス出来る権限を作成していきます。

AWSコンソールのAWSサービスから「IAM」を選択して左メニューの「ロール」を選択し、「新しいロールの作成」ボタンを押します。
ロール名は何でもいいのですが、今回は「humuhumu-LambdaExecutionRole」と命名しました。

次に、ロールタイプを選択するのですが、あとで個別にロールを記述するため、ここで設定した管理ロールは削除するため適当でもいいのですが、個別記述が面倒くさい人は「AWSサービスロール」→「AWS Lambda」を選択、ボリシー名「AmazonDynamoDBFullAccess」を選択してロールを作成してください。

きちんと制限を付けて設定したい方は、先ほど設定した管理ポリシーを削除して以下のインラインポリシーを設定してください。

ポリシー名：LambdaBasicExecutionPlusDynamoDB
ポリシードキュメント：

```
{
    "Statement": [
        {
            "Resource": "arn:aws:logs:*:*:*",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Effect": "Allow"
        },
        {
            "Resource": "arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXXX:table/humuhumus",
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query",
                "dynamodb:DeleteItem"
            ],
            "Effect": "Allow"
        }
    ],
    "Version": "2012-10-17"
}
```

ポリシードキュメントにある「arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXXX:table/humuhumus」の箇所は先ほど作成したDynamoDBのARN（Amazonリソースネーム）を設定してください。

![DynamoDB · AWS Console.png](https://qiita-image-store.s3.amazonaws.com/0/141818/fa839f7a-7f68-1c67-fa63-f08272f9c9e7.png)

###Lambda作成

次に、先ほど作成したDynamoDBに書き込みと参照する２種類のLambda関数を作成します。

はじめにふむふむ投票を行うPost関数を作成します。

AWSコンソールからAWSサービスの「Lambda」を選択、「Create a Lamobda function」ボタンを押します。
最初に表示される「Select blueprint」はテンプレート集なのでここはSkipします。
次に表示される「Configure triggers」も、この時点では接続しないので何もせずに「Next」を押します。
次に表示される「Configure function」画面でnameに「humuhumus-post」を入力。
Rantimeを「Node.js 4.3」を選択して
Lambda function codeに以下のコードを記述します。

```
var AWS, docClient;

AWS = require('aws-sdk');

docClient = new AWS.DynamoDB.DocumentClient();

exports.handler = function(event, context) {
  var item, param, uuid;
  if (event.humuId) {
    return context.fail("humuId cannot not be specified");
  } else {
    uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r, v;
      r = Math.random() * 16 | 0;
      v = c === 'x' ? r : r & 0x3 | 0x8;
      return v.toString(16);
    });
    item = event;
    item.humuId = uuid;
    item.createdAt = Math.floor(new Date / 1000);
    param = {
      TableName: 'humuhumus',
      Item: item
    };
  }
  docClient.put(param, function(err, data) {
    if (err) {
      return context.fail(err);
    } else {
      return context.succeed(item);
    }
  });
};
```
このスクリプトを簡単に説明すると、受け取ったパラメータのオブジェクトにIDと日付を生成して追記してテーブル名「humuhumus」に追加（put）しています。

同様に、今度はテーブル情報を参照する関数を作成します。上記と同じように進み
関数名（name）を「humuhumus-get-group-all」にしてコードを

```
var AWS, docClient;

AWS = require('aws-sdk');

docClient = new AWS.DynamoDB.DocumentClient();

exports.handler = function(event, context) {
  var param;
  if (event.groupId) {
    param = {
      TableName: 'humuhumus',
      FilterExpression : "#key = :key",
      ExpressionAttributeNames  : {"#key" : "groupId"},
      ExpressionAttributeValues : {":key" : event.groupId}
    };
  } else {
    return context.fail("groupId is not specified");
  }
  docClient.scan(param, function(err, data) {
    if (err) {
      return context.fail(err);
    } else if (data.Count) {
      return context.succeed(data);
    } else {
      return context.fail("item not found");
    }
  });
};
```
こちらのコードの説明は、テーブル名「humuhumus」よりgroupIdでフィルタリングしたitem一式を取得してそのリストを返します。

正しく動作しているかどうかを、テストしてみてください。テスト方法は上部にある「Actions」プルダウンより「Configure test event」を押し表示された入力欄にJsonパラメータを渡してテストを行います。
![スクリーンショット 2016-09-27 15.50.50.png](https://qiita-image-store.s3.amazonaws.com/0/141818/2c4bf1d6-0d46-74d1-9542-b01647ce15e4.png)

正しくテストが完了すると以下のように表示されます。
![Lambda Management Console.png](https://qiita-image-store.s3.amazonaws.com/0/141818/c52915e4-bcb1-3020-9bb4-1b7f97fcd198.png)

エラーになる場合は、おそらくIAMロールの設定が間違っている可能性が高いので、再度見直してみてください。

###API Gateway作成

最後に、ブラウザからのリクエストの受付先となるAPI Gatewayの作成を行います。
従来の構成だとApacheの機能に近いものになります。

今回作成するのは投票を受け付けるPOSTメソッドと、指定グループIDのリストを受け取るGETメソッドの２つを作成します。
それぞれ以下のようなURLになるように設計します。

投票（POSTメソッド）： ~/v1/humuhumus/
受取（GETメソッド） ： ~/v1/humuhumus/{groupId}
※ちなみに「v1」ディレクトリはバージョン１を表すAPI設計でよくある手法です。将来バージョンアップを見越すのであれば付けておくと何かと便利です。

上記のようなリソース状況になるようにAPI Gatewayの設定を行っていきます。

AWSコンソールのAWSサービスより「API Gateway」を選択、「APIの作成」ボタンを押します。
新しいAPIとしてAPI名を「humuhumus」でAPIの作成を行います。
作成されたAPI画面で「アクション」プルダウンより「リソースの作成」を選択して「v1」リソースを追加します。
![スクリーンショット 2016-09-27 16.16.40.png](https://qiita-image-store.s3.amazonaws.com/0/141818/335a02da-2dca-c376-4ddb-d806225664c1.png)

同様に「humuhumus」リソースもv1の下位になるように追加します。
![スクリーンショット 2016-09-27 16.17.06.png](https://qiita-image-store.s3.amazonaws.com/0/141818/0debeb8d-3014-54e6-5a7b-8c27b5e00624.png)

最後にhumuhumusフォルダの下位に「{groupId}」リソースを追加します。
![スクリーンショット 2016-09-27 16.19.27.png](https://qiita-image-store.s3.amazonaws.com/0/141818/d614f324-0c02-b7ff-b45a-b3a88a0d10f2.png)
この時、リソースパスには{}で囲んでgroupIdを記述します。こうすることで動的なパラメータとして扱うことが出来ます。
※リソースパスは大文字が小文字に自動変換されるので注意してください。「groupId」→「groupid」となるので修正しておいてください。

これでリソースの作成が完了しました。次にメソッドを追加していきます。

はじめに投票（POST）を行うメソッドを追加します。
先ほど作成したリソースの「/humuhumus」を選択して「アクション」プルダウンより「メソッドの作成」を選択します。
「/humuhumus」の下にプルダウンが表示されるのでプルダウンの中から「POST」を選択してチェックマークをクリックします。
![スクリーンショット 2016-09-27 16.41.50.png](https://qiita-image-store.s3.amazonaws.com/0/141818/eb93ae63-1a01-145a-11a9-1cbb0869b9d8.png)

統合タイプを「Lambda 関数」
Lambdaリージョンを「東京リージョン（ap-northeast-1）」
Lambda関数を先ほど作成した「humuhumus-post」
に設定して保存します。
![スクリーンショット 2016-09-27 16.42.23.png](https://qiita-image-store.s3.amazonaws.com/0/141818/b2128dd2-5db7-77fc-3f5e-78fedcfcacdb.png)

一旦、正しく設定出来たかテストを行ってみましょう。
作成したPOSTメソッドをクリックして、「テスト」のアイコンをクリックしてください。
![API Gateway.png](https://qiita-image-store.s3.amazonaws.com/0/141818/ad80b91e-f851-8586-2a38-c2fc2afdf914.png)
表示された画面のリクエスト本文に以下のJsonを入力してテストを行ってください。

```
{
    "groupId": "1"
}
```
正しく疎通できたらステータス２００でレスポンスが帰ってきます。
***
次に、groupIdをGETパラメータとしてデータを取得するインターフェイスを作成します。
リソースの「/{groupId}」を選択して「アクション」プルダウンより「メソッドの作成」を選択します。
「/{groupId}」の下にプルダウンが表示されるのでプルダウンの中から「GET」を選択してチェックマークをクリックします。
![スクリーンショット 2016-09-27 16.47.52.png](https://qiita-image-store.s3.amazonaws.com/0/141818/80a7d690-87be-c45c-9d07-0b344436297d.png)

統合タイプを「Lambda 関数」
Lambdaリージョンを「東京リージョン（ap-northeast-1）」
Lambda関数を先ほど作成した「humuhumus-get-group-all」
に設定して保存します。
![スクリーンショット 2016-09-27 16.48.15.png](https://qiita-image-store.s3.amazonaws.com/0/141818/d0bb3725-3286-c689-14f9-cab9aea09a54.png)

GETでパラメータを渡す場合はURLパラメータとのマッピングを行う必要があります。
作成したGETメソッドをクリックして現れた画面の「統合リクエスト」をクリックしてください。
![API Gateway2.png](https://qiita-image-store.s3.amazonaws.com/0/141818/32306a01-d761-fda0-8c18-6a10fc9ff3f0.png)

表示された画面の「本文マッピングテンプレート」を展開して「マッピングテンプレートの追加」を押してください。
Content-Typeに「application/json」を入力してチェックを入れ、以下のマッピングを設定します。
![スクリーンショット 2016-09-27 16.59.13.png](https://qiita-image-store.s3.amazonaws.com/0/141818/df6e1999-296e-6257-6b23-c37ab2053eb3.png)

```
{"groupId":"$input.params('groupId')"}
```

正しく設定出来たか、前のPOSTと同じくテストを行ってみましょう。
ステータスが２００で帰ってきたらOKです。

以上でAWS側の設定は完了となります。

次回は、クライアント側のReact.jsについて記載する予定です。