DynamoDB + S3 + lambda + APIGateway で超簡単なクラウドサービス構築メモ

機会があったので、AWSの各サービスを用いて簡単なクラウドサービスを構築してみました。
イメージとしては、
とあるセンサデバイスから取得したデータ(今回は照度と土壌温度を取得するという体で)をDynamoDBから取り出してindex.htmlに表示するという超簡単なものです。

まず、どのようなサービスを使ったか、ですが
今回はタイトルにもあるように

- S3
- DynamoDB
- Lambda
- API Gateway

を使いました。


各サービスの設定を以下に簡単にまとめておきます。

# S3
S3はデータを格納するバケットの役割を果たすことが多く知られていると思いますが、静的ページのホスティングもできるそうです。
S3の静的ホスティングはクライアントサイドの言語しか使えないそうですので、私はhtmlとjavascriptのみでページを実装してます。
というわけで今回はその機能を利用してみました。

① まずはS3バケットを作成します。
![s3_1.PNG](https://qiita-image-store.s3.amazonaws.com/0/186104/b86d0fa1-280e-0e80-ad6f-a4bf04845743.png)
バケット名はURLで使われるので熟考しておきましょう。
他の設定はとりあえず今回は無し。

② ルートページとなるindex.htmlを準備します。
![index.PNG](https://qiita-image-store.s3.amazonaws.com/0/186104/376d281a-b259-26eb-14cf-394ea865f72f.png)
ひとまずはこんな感じで。作成したindex.htmlを先ほどつくったバケットにアップロードします。ここでも特に設定は無し。
index.htmlは後ほど修正します。

③ 作成したバケットのプロパティタブを開き。Static website hostingを選択し、インデックスドキュメントの設定をします。
![s3_2.PNG](https://qiita-image-store.s3.amazonaws.com/0/186104/d17661a4-726e-7b52-d50e-4de5957a0794.png)
![s3_3.PNG](https://qiita-image-store.s3.amazonaws.com/0/186104/ab396cc8-5a9c-5467-494b-2ae9c4100de5.png)
インデックスドキュメントに、先ほど追加したindex.htmlを記載し、保存。

④ アクセス権限タブのバケットポリシーエディタに以下を記載します。
![s3-4.PNG](https://qiita-image-store.s3.amazonaws.com/0/186104/b5478aab-d6f9-e589-13ec-8a04e0f0f75a.png)

```
{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "samplesite",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::test-bucket-study/*"
        }
    ]
}
```
⑤ エンドポイントに記載されているURLにアクセスしてみると、文字が出てきました。
![s3_5.PNG](https://qiita-image-store.s3.amazonaws.com/0/186104/25903776-453f-02c8-b754-b5ed33ea1ca1.png)


ホスティングの設定はここまで。
後ほどindex.htmlは編集します。

# DynamoDB
次に、データの取得元となるDBを構築します。
今回は試しに以下のような構成のdataTableを作成しました。
![dynamodb_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/186104/29ae7255-3226-3a10-fd10-c7ff09853605.jpeg)
deviceIdがパーティションキーです。
DBから値が取得できることを確認するために、テストデータを入れておきましょう。
今回はセンサデバイスから照度と土壌温度を取得することを想定し、こんな項目にしてます。


# Lambda
続いて、センサデバイスがDynamoDBに格納したデータを取り出して画面側に渡す関数をLambdaで作成します。
LambdaはAPIGatewayで設定されたAPIが叩かれると発火するようトリガーを設定します。
とりあえずはランタイムを"Node.js 8.10"としてindex.jsを以下のように作成します。

```javascript:index.js
'use strict';

var url = require('url');
var aws = require('aws-sdk');
var dynamo = new aws.DynamoDB.DocumentClient({region: 'ap-northeast-1'});
var tableName = "dataTable";

exports.handler = function(event, context) {
        console.log('get event');
        dynamo.scan({
          TableName: tableName,
          FilterExpression:
             "contains(deviceId, :id)", //"id"というdeviceIdが含まれたデータを対象とする
          ExpressionAttributeValues: {
            ":id": "test" //"id"という変数に"test"を設定
          }}, function (err, data) {
                if (err) {
                    console.log(err,err.stack);
                    context.fail(err);
                } else {
                    console.log(JSON.stringify({event: event, context: context}, null, 2));
                    context.succeed(data);
                }
        });
};
```
以上がdataTableというテーブルから、testというidのデータを取得する関数になります。
詳細な記法等は他の方が作成された記事を御確認ください。

ハンドラ名の修正も必要になります。
ハンドラ名は、"ファイル名.handler"となりますので、今回は"index.handler"となります。

以下、作成したLambdaの画面です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/186104/13954425-2607-cff0-f2ed-a730ec5957f0.png)

加えてロールの設定もしておきます。

①IAMを開き、ロールを選択して"ロールの作成"を選択。
②"信頼されたエンティティの種類を選択"→AWSサービス、"このロールを使用するサービスを選択"→lambda を選ぶ。
③ポリシーのフィルタでは "AmazonDynamoDBReadOnlyAccess"を選択。
④ロール名を適当に決めて作成。

上記手順にて作成したロール"getDynamoDBData"をlambdaに紐づけました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/186104/5e14eda7-95e4-41f8-e908-2b452fc80438.png)


lambdaは以上になります。

#API Gateway
API GatewayでAPIを作成します。

①APIの作成をクリック
②"新しいAPIの作成"で、適当な名前をつけます。私は今回"testAPI"という名前にしました。
③APIが作成できたら、さっそく新規作成したAPIのルート部分をクリックします。
④"アクション"タブをクリックし、子リソースを作成します。今回は名前を"test"としましょう。
⑤"アクション"タブをクリックし、"メソッドの作成"を選びます。すると以下のような感じでプルダウンが表示されるため、"GET"を選択。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/186104/7745d4ac-992f-19e0-7217-fbe42edcbded.png)
⑥GETメソッドの設定を行います。リソース欄から先ほど作成した"GET"をクリックし、以下のように設定します。(lambdaは先ほど作成したもの)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/186104/5112bfd4-0117-8101-1a61-e7ea77ea1efe.png)
⑦こんな感じの画面になるかと思います。そうしたら次はリソース欄のルート部分をクリックした上でアクションタブを開きます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/186104/4e29b639-045f-2fec-5c71-446efa7954a0.png)
⑧"CORSの有効化"を選択して、そのまま置換を完了してしまいましょう。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/186104/91d939ae-5547-585b-f9cf-a06944e87223.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/186104/6cdad5a3-a955-5755-653d-2fc6f3235bae.png)
⑨続いてステージで"prod"を作成します。ステージは一番左のAPI一覧で、該当APIをクリックすると出現します。

基本的な設定は以上になります。
ちなみに、今回は"ステージ"でprodを作成し、子リソースて"/test"を作成したため、作成したAPIのURLは
https://～～～amazonaws.com/prod/test となります。
https://～～～amazonaws.com/prodの部分は、prod ステージエディターでチェックしてください。

#index.htmlの作成
最後に最初にS3に保管したindex.htmlを、データが表示できるよう修正します。


```html:index.html
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.7.16.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">

<script type="text/javascript" src="js/hmac-sha256.js"></script>
<script type="text/javascript" src="js/sha256.js"></script>
<script type="text/javascript" src="js/hmac.js"></script>
<script type="text/javascript" src="js/enc-base64.js"></script>
<script type="text/javascript" src="js/url-template.js"></script>
<script type="text/javascript" src="js/sigV4Client.js"></script>
<script type="text/javascript" src="js/apiGatewayClient.js"></script>
<script type="text/javascript" src="js/simpleHttpClient.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/apigClient.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<script  type="text/javascript">

var apigClient;

$(function(){
	//API Clientを初期化
	  apigClient = apigClientFactory.newClient();
	});

function test(){
    return $.ajax({
        type: 'GET',
        url: 'https://~~~~.amazonaws.com/prod/test',
        crossDomain: true
    })
}

test().done(function(result) {
	var dataString = JSON.stringify(result);
	var dataArray = JSON.parse(dataString);
   	 console.log(result);
   	 $('#test').text(dataArray.Items[0].illumination);
   	 console.log('OK');
	}).fail(function(result) {
	console.log('NG');
	});


$(function(){
	$("#displayMap").css({
			'max-height':'100%',
			'max-width':'100%',
			'background-repeat': 'no-repeat',
			'background-size': '100% 80%',
			'background-position': '5% bottom',
			'margin': '0 auto',
			'position': 'relative',
			'background-image': 'url(picture/map.jpg)'
		});
	$("#test").css({
			'position':'absolute',
			'right':'39%',
			'top':'47%',
			'width':'75px',
			'font-size':'30px',
			'background-color': '#ffffff'
		});
});

</script>
</head>

<body>
	<div id = "displayMap" style=" position:relative; width:55vw; height:calc(55vw * 2/3)">
			<span style="position:absolute; right:47%; top:47%; font-size:30px">照度</span>
			<span id="test" style="border: 1px solid #333333;"></span>
			<span style="position:absolute; right:36%; top:47%;font-size:30px">lx</span>
	</div>
</body>
</html>
```

とりあえずDBに入っている照度をとってきて表示できるようなプログラムです。
inportにはとりあえず今後必要になりそうなjsをつぎ込んでいます。
指定しているjsはS3バケットの、index.htmlと同じ階層にjsフォルダを作成し、そこに保存しておきました。

HTMLはまったく慣れていないためかなり雑なつくりですが、注目していただきたい部分はこちらです。

```html:index.html
function test(){
    return $.ajax({
        type: 'GET',
        url: 'https://~~~~~.amazonaws.com/prod/test',
        crossDomain: true
    })
}

test().done(function(result) {
	var dataString = JSON.stringify(result);
	var dataArray = JSON.parse(dataString);
   	 console.log(result);
   	 $('#test').text(dataArray.Items[0].illumination);
   	 console.log('OK');
	}).fail(function(result) {
	console.log('NG');
	});
```

こちらで参照するAPIの設定と、取得したデータから照度のみ取得する処理を行っています。

上記をS3にあげなおして、再度更新してみましょう。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/186104/ed159ee2-225f-d6a0-5132-826fe6404798.png)


うまく表示できました！！