はじめに
社内でいつも通り仕事をしていると、約1週間で簡単な投票アプリを作ってくれないか?というオファーを受け、構成を考えているときに「Serverless Frameworkを使えばいいじゃん」と神のお告げが降ってきましたので、触ってみることにしました。
最終目標
スマホから投票できて、最終的にCSVでまとまった投票データを吐き出す
構成はこんな感じ
青枠の部分がServerlessFramework
大まかな流れ
- S3に投票フォームをアップロードしてバケットごとWeb公開する
- フォームの投票内容をAPIGateWayに向かってPOSTする
- APIGateWayがLambdaをキックする
- Lambdaが受け取ったJSONをDynamoDBに書き込む
- 完了!
さあ、はじめてみようか
書いていく前に・・・今回ServerlessFrameworkを触るにあたって@hiroshik1985様のとことんサーバーレス①:Serverless Framework入門編を参考にさせていただきました。
ServerlessFrameworkのインストール
npmで入れちゃいます
$ npm install -g serverless
AWS Credentialsを設定する
ServerlessFramework用のIAMを作成して、AWSConfigureに登録します
$ aws configure
AWS Access Key ID [None]: 先ほど作成したIAMのアクセスキー
AWS Secret Access Key [None]: 先ほど作成したIAMのシークレットアクセスキー
Default region name [None]: ap-northeast-1
Default output format [None]: ENTER
プロジェクト作成
serverless用のディレクトリを作成し、そのディレクトリの中で下記のコマンド実行
今回はnode.jsで
※serverlessコマンドはインストール時に用意されるslsエイリアスを使うと便利です
$ sls create --template aws-nodejs --name vote
_______ __
| _ .-----.----.--.--.-----.----| .-----.-----.-----.
| |___| -__| _| | | -__| _| | -__|__ --|__ --|
|____ |_____|__| \___/|_____|__| |__|_____|_____|_____|
| | | The Serverless Application Framework
| | serverless.com, v1.8.0
-------'
Serverless: Successfully generated boilerplate for template: "aws-nodejs"
こんなのが表示されたら成功。なんかかっこいい・・・
これでディレクトリ内にhandler.jsやserverless.ymlが作成される
はじめてのデプロイ
デプロイのコマンドはこちら
$ sls deploy -v
特に怒られれなければデプロイ成功です。AWSのコンソール画面ですでにLambda等が立ち上がっていると思います
Lambdaファンクションを書くよ
Lambdaファンクションは基本的にhandler.jsに書きます。
たとえばこんな風に
import AWS from 'aws-sdk'
AWS.config.update({region: 'ap-northeast-1'})
const db = new AWS.DynamoDB.DocumentClient()
export const register = (event, context, callback) => {
const body = JSON.parse(event.body)
const params = {
TableName: "names",
Key: {
id: body.employeeNumber
},
UpdateExpression: "set #Group1 = :vote1, #Group2 = :vote2, #Group3 = :vote3",
ExpressionAttributeNames: {
"#Group1": "voteGroup1",
"#Group2": "voteGroup2",
"#Group3": "voteGroup3"
},
ExpressionAttributeValues: {
":vote1": body.voteGroup1,
":vote2": body.voteGroup2,
":vote3": body.voteGroup3
},
ReturnValues: "UPDATED_NEW"
}
try {
db.update(params, (error, data) => {
if (error) {
callback(null, {
statusCode: 400,
headers:{ "Access-Control-Allow-Origin" : "*" },
body: JSON.stringify({message: 'Failed.', error: error, params: params})
})
}
callback(null, {
statusCode: 200,
headers:{ "Access-Control-Allow-Origin" : "*" },
body: JSON.stringify({message: 'Succeeded!', params: params})
})
})
} catch (error) {
callback(null, {
statusCode: 400,
headers:{ "Access-Control-Allow-Origin" : "*" },
body: JSON.stringify({message: 'Failed.', error: error, params: params})
})
}
}
フォームからPOSTメソッドでJSON形式のデータをDynamoDBに格納します
送られてくるJSONデータは{"employeeNumber": "001", "voteGroup1": "1", "voteGroup2": "2", "voteGroup3": "3"}のような形で送られてくることを想定しています
そのほか設定を書くよ
serverless.ymlにDynamoDBやLambdaなどの設定を書きます
serverlessFrameworkの設定ファイルみたいなものです
たとえばこんな風に
service: vote
provider:
name: aws
runtime: nodejs4.3
stage: dev
region: ap-northeast-1
iamRoleStatements:
- Effect: "Allow"
Resource: "arn:aws:dynamodb:${self:provider.region}:*:table/*"
Action:
- "dynamodb:*"
plugins:
- serverless-webpack
// LambdaFunction
functions:
register:
handler: handler.register
events:
- http:
path: names
method: post
cors: true
// DynamoDB
resources:
Resources:
hello:
Type: "AWS::DynamoDB::Table"
Properties:
TableName: names
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
これで再度デプロイするとLambdaファンクションが設定され、DynamoDBにテーブルが作成されます
試しに実行だ
デプロイ後に表示されるAPIGateWayのエンドポイントに対してcurlでリクエストを送信します
※「 https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/names 」はAPIGateWayのエンドポイントです
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"employeeNumber": "001", "voteGroup1": "1", "voteGroup2": "2", "voteGroup3": "3"}' https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/names
問題なくリクエストが送信されれば、DynamoDBにテータが格納されていると思います
フォームの作成
あまり手の込んだフォームをコーディングしている時間がなかったので、シンプルにまとめました
CSSは「Material Design Lite」というCSSフレームワークを使用し、AjaxでAPIGatewayのエンドポイントに対してHTTPリクエストを投げています
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>投票アプリ</title>
<script type="text/javascript" src="jquery-3.1.1.min.js"></script>
<script type="text/javascript" src="vote.js"></script>
<script type="text/javascript" src="mdl/material.min.js"></script>
<link rel="stylesheet" href="mdl/material.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="common.css">
</head>
<body>
<!-- Always shows a header, even in smaller screens. -->
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
<header class="mdl-layout__header">
<div class="mdl-layout__header-row">
<!-- Title -->
<span class="mdl-layout-title">投票アプリ</span>
<!-- Add spacer, to align navigation to the right -->
<div class="mdl-layout-spacer"></div>
</div>
</header>
<main class="mdl-layout__content">
<div id="page-content" class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
社員番号:<br>
<select name="employee_number" class="employee-number">
<option value="">選択してください</option>
<option value="001">001</option>
<option value="002">002</option>
<option value="003">003</option>
<option value="004">004</option>
<option value="005">005</option>
<option value="006">006</option>
</select>
<span class="font-red">※必須</span>
</div>
<div class="mdl-cell mdl-cell--12-col">
投票するグループを<span class="font-red">3つ</span>選択してください<span class="font-red">※必須</span>
<br>
1位:
<select name="vote_group1" class="vote-group1">
<option value="">選択してください</option>
<option value="1">グループ1</option>
<option value="2">グループ2</option>
<option value="3">グループ3</option>
<option value="4">グループ4</option>
<option value="5">グループ5</option>
<option value="6">グループ6</option>
<option value="7">グループ7</option>
<option value="8">グループ8</option>
<option value="9">グループ9</option>
</select>
<br>
<br>
2位:
<select name="vote_group2" class="vote-group2">
<option value="">選択してください</option>
<option value="1">グループ1</option>
<option value="2">グループ2</option>
<option value="3">グループ3</option>
<option value="4">グループ4</option>
<option value="5">グループ5</option>
<option value="6">グループ6</option>
<option value="7">グループ7</option>
<option value="8">グループ8</option>
<option value="9">グループ9</option>
</select>
<br>
<br>
3位:
<select name="vote_group3" class="vote-group3">
<option value="">選択してください</option>
<option value="1">グループ1</option>
<option value="2">グループ2</option>
<option value="3">グループ3</option>
<option value="4">グループ4</option>
<option value="5">グループ5</option>
<option value="6">グループ6</option>
<option value="7">グループ7</option>
<option value="8">グループ8</option>
<option value="9">グループ9</option>
</select>
<br>
</div>
<div class="mdl-cell mdl-cell--12-col">
<button id="vote-button"
class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored">投票
</button>
</div>
<div class="mdl-cell mdl-cell--12-col">
<textarea id="response" disabled></textarea>
</div>
</div>
</main>
</div>
</body>
</html>
.font-red {
color: red;
font-weight: bold;
font-size: 16px;
}
$(function () {
$("#response").html("Response Values");
/**
* 投票ボタンを押したときの処理
*/
$("#vote-button").click(function () {
var url = 'https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/names';
var employeeNumber = $(".employee-number").val();
if (employeeNumber === "") {
alert("社員番号は必須です");
return;
}
/**
* 選択肢の登録数のバリデーション
*/
var voteGroup = {
group1: $(".vote-group1").val(),
group2: $(".vote-group2").val(),
group3: $(".vote-group3").val()
};
var count = 0;
$.each(voteGroup, function (key, val) {
if (val !== "") {
count++;
}
});
if (count < 3) {
alert("グループを3つ選択してください");
return
}
/**
* 重複チェック
*/
if(voteGroup.group1 === voteGroup.group2){
alert("同じクループは選択できません");
return
}
if(voteGroup.group1 === voteGroup.group3){
alert("同じクループは選択できません");
return
}
if(voteGroup.group2 === voteGroup.group3){
alert("同じクループは選択できません");
return
}
var JsonData = {
employeeNumber: employeeNumber,
voteGroup1: voteGroup.group1,
voteGroup2: voteGroup.group2,
voteGroup3: voteGroup.group3
};
alert(JSON.stringify(JsonData));
$.ajax({
type: 'post',
url: url,
data: JSON.stringify(JsonData),
contentType: 'application/JSON',
dataType: 'JSON',
scriptCharset: 'utf-8',
success: function (data) {
window.location.href = "thankYou.html";
},
error: function (data) {
// Error
alert("error");
alert(JSON.stringify(data));
$("#response").html(JSON.stringify(data));
}
});
});
});
これらのファイルをWeb公開したS3バケットにアップロードし、S3のエンドポイントにアクセスし画面を確認します
あとはフォームの項目を入力し、「投票ボタン」をクリックすると・・・
無事成功すればDynamoDBにPOSTしたJSONが追加されています
また要望により、同じ社員番号で投票すると内容を更新できるようにしています
Web公開したS3のエンドポイントに対してドメインを向けてやるとさらにいい感じですね。
S3に独自ドメインを登録する方法は@Ichiro_Tsuji様の独自ドメインを使ってAmazon S3で静的Webサイトをホストするを参照してください
さいごに
いかんせん1週間とはいえ通常業務の合間を縫ってやっていたので、かなり雑な処理になっていると思います(汗)
しかも最後のCSV吐き出しはコンソールから生成するというなんともいけてない感じ・・・
次回いつ使うかわからないけど、改善に励んでいこうとおもっております!
ちなみにServerlessFrameworkで作成した環境を消去するときは下記を実行すればよいみたいです
sls remove
もっといい感じになったらまた記事書き直すんだ・・・