AWSに代表されるServerless Architectureはクラウド上での動作が前提ですが、Serverless Frameworkのプラグインを用いることにより、ローカル環境でも動作させることが可能になるのでご紹介します。AWSにデプロイすることなく開発が可能になるので、より素早く開発ができます。また、AWSのアカウントを持っていない方もServerlessの世界を体験できるかと思います。ここではじゃんけんを行うAPIの開発を通して、ローカルでの開発方法を説明します。完成版のソースコードは以下にあります
構成
API Gateway、Lambda、DynamoDBを用いたアーキテクチャをここでは想定します。ローカル開発環境ではそれぞれ serverless-offline、javascriptファイル、DynamoDB Local が対応します。
環境
- macOS sierra
- Node.js v4.6.2
- Serverless Framework v1.20.1
プロジェクトの作成
Serverless Frameworkを用いて開発するのでインストールを行い、新しいサービスを作成します。
$ npm install -g serverless
$ mkdir serverless-janken
$ sls create -t aws-nodejs -n serverless-janken
関連するパッケージのインストール
API Gatewayの代用として利用する serverless-offline プラグイン、Serverless FrameworkからDynamoDB Localを操作できるようにする serverless-dynamodb-local プラグインをインストールします。
$ npm install aws-sdk
$ npm install --save-dev serverless-offline
$ npm install --save-dev serverless-dynamodb-local
インストール後、Serverless Frameworkからプラグインとして利用できるように設定に記入します。
$ vi serverless.yml
# service: serverless-janken の下に以下を追記
plugins:
- serverless-dynamodb-local
- serverless-offline
DynamoDB Local のインストール
serverless-dynamodb-local プラグインを利用してDynamoDB Localをインストールします。
$ sls dynamodb install
DynamoDB Local テーブルの定義
DynamoDB Localで利用するテーブルを定義します。サンプルとして、プレイヤー名とUnixtimeをキーとするテーブルを作成しました。
$ mkdir migrations
$ vi migrations/jankens.json
# 下記内容で保存する
[
{
"player": "user1",
"unixtime": 1482418800,
"player_hand": "rock",
"computer_hand": "paper",
"judge": "lose"
}
]
DynamoDB Local の起動
DynamoDB Local起動時にテーブルの作成とシードデータの挿入を行うため、 serverless.yml
に設定を入れます。
# service: serverless-janken の下に以下を追記
custom:
dynamodb:
stages:
- dev
start:
port: 8000
inMemory: true
migrate: true
seed: true
seed:
development:
sources:
- table: jankens
sources: [./migrations/jankens.json]
resources:
Resources:
JankensTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: jankens
AttributeDefinitions:
- AttributeName: player
AttributeType: S
- AttributeName: unixtime
AttributeType: N
KeySchema:
- AttributeName: player
KeyType: HASH
- AttributeName: unixtime
KeyType: RANGE
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
DynamoDB Localを起動します。
$ sls dynamodb start
Dynamodb Local Started, Visit: http://localhost:8000/shell
Serverless: DynamoDB - created table jankens
Seed running complete for table: jankens
ブラウザで http://localhost:8000/shell にアクセスし、テーブルの中身を確認します。左側のエディタに下記を記入し、再生ボタンを押します。
var params = {
TableName: 'jankens',
};
dynamodb.scan(params, function(err, data) {
if (err) ppJson(err);
else ppJson(data);
});
図のように右側にシードデータの内容が確認できれば、テーブルの作成&シードの挿入が完了しています。
Lambdaの開発
データベースの設定ができたので、ロジック部分を書いていきます。handler.js
を開いて以下の内容で保存します。じゃんけんを行うAPI playJanken
とじゃんけん結果を参照するAPI listJankens
のためのロジックを書いています。このコードはAWSでもローカルでも動作が可能なように、 process.env.IS_OFFLINE
を見て接続するDynamoDBを切り替えています。
"use strict";
var AWS = require("aws-sdk");
var judgeJanken = function (a, b) {
var c = (a - b + 3) % 3;
if (c === 0) return "draw";
if (c === 2) return "win";
return "lose";
}
var getDynamoClient = function (event) {
var dynamodb = null;
if (process.env.IS_OFFLINE) {
dynamodb = new AWS.DynamoDB.DocumentClient({
region: "localhost",
endpoint: "http://localhost:8000"
});
} else {
dynamodb = new AWS.DynamoDB.DocumentClient();
}
return dynamodb;
}
module.exports.playJanken = function (event, context, callback) {
console.log("Received event:", JSON.stringify(event, null, 2));
console.log("Received context:", JSON.stringify(context, null, 2));
var dynamodb = getDynamoClient(event);
var date = new Date();
var unixtime = Math.floor(date.getTime() /1000);
var hand = ["rock", "scissors", "paper"];
var player_name = event.queryStringParameters.name;
var player_hand = event.queryStringParameters.hand;
var player = hand.indexOf(player_hand);
var computer = Math.floor( Math.random() * 3) ;
var judge = judgeJanken(player, computer);
var params = {
TableName: "jankens",
Item: {
player: player_name,
unixtime: unixtime,
player_hand: player_hand,
computer_hand: hand[computer],
judge: judge
}
};
dynamodb.put(params, function(err) {
var response = {statusCode: null, body: null};
if (err) {
console.log(err);
response.statusCode = 500;
response.body = {code: 500, message: "PutItem Error"};
} else {
response.statusCode = 200;
response.body = JSON.stringify({
player: player_hand,
computer: hand[computer],
unixtime: unixtime,
judge: judge
});
}
callback(null, response);
});
};
module.exports.listJankens = function (event, context, callback) {
console.log("Received event:", JSON.stringify(event, null, 2));
console.log("Received context:", JSON.stringify(context, null, 2));
var dynamodb = getDynamoClient(event);
var params = { TableName: "jankens" };
dynamodb.scan(params, function(err, data) {
var response = {statusCode: null, body: null};
if (err) {
console.log(err);
response.statusCode = 500;
response.body = {code: 500, message: "ScanItem Error"};
} else if ("Items" in data) {
response.statusCode = 200;
response.body = JSON.stringify({jankens: data["Items"]});
}
callback(null, response);
});
};
API Gatewayの設定
最後に前項で作成したロジックを呼ぶエンドポイントを作成するために serverless.yml
に設定を入れます。
- GET /jankens... じゃんけん結果の参照
- POST /jankens... じゃんけんを行い結果をDynamoDB Localに保存
service: serverless-janken
custom:
dynamodb:
start:
port: 8000
inMemory: true
migrate: true
seed: true
seed:
development:
sources:
- table: jankens
sources: [./migrations/jankens.json]
plugins:
- serverless-dynamodb-local
- serverless-offline
provider:
name: aws
runtime: nodejs4.3
functions:
playJanken:
handler: handler.playJanken
events:
- http:
path: jankens
method: post
listJankens:
handler: handler.listJankens
events:
- http:
path: jankens
method: get
resources:
Resources:
JankensTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: jankens
AttributeDefinitions:
- AttributeName: player
AttributeType: S
- AttributeName: unixtime
AttributeType: N
KeySchema:
- AttributeName: player
KeyType: HASH
- AttributeName: unixtime
KeyType: RANGE
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
テスト
以上で、データベース、ロジック、エンドポイントがそろったのでローカルで起動させて利用してみます。
$ sls offline
別のシェルでcurlでAPIを叩いて利用してみます。うまくいかない場合は sls dynamodb start
でDynamoDB Localが起動していることを確認してください。
$ curl 'http://localhost:3000/jankens?hand=rock&name=test' -X POST
{"player":"rock","computer":"scissors","unixtime":1482469235,"judge":"win"}
$ curl 'http://localhost:3000/jankens'
{"jankens":[{"unixtime":1482469235,"player_hand":"rock","judge":"win","player":"test","computer_hand":"scissors"},{"unixtime":1482418800,"player_hand":"rock","judge":"lose","player":"user1","computer_hand":"paper"}]}
AWSにデプロイ
ローカルで開発ができたら、AWS上にデプロイします。AWSで動かすには、DynamoDBのテーブル定義と、IAMロールの設定が必要でしたので、serverless.yml
に追記して下記のようにします。
service: serverless-janken
custom:
dynamodb:
start:
port: 8000
inMemory: true
migrate: true
seed: true
seed:
development:
sources:
- table: jankens
sources: [./migrations/jankens.json]
plugins:
- serverless-dynamodb-local
- serverless-offline
package:
exclude:
- node_modules/**
- migrations/**
- .git/**
provider:
name: aws
runtime: nodejs4.3
# DynamoDBの利用の許可
iamRoleStatements:
- Effect: 'Allow'
Action:
- 'dynamodb:PutItem'
- 'dynamodb:Scan'
Resource: '*'
functions:
playJanken:
handler: handler.playJanken
events:
- http:
path: jankens
method: post
listJankens:
handler: handler.listJankens
events:
- http:
path: jankens
method: get
# DynamoDB Tableの作成
resources:
Resources:
JankensTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: jankens
KeySchema:
- AttributeName: player
KeyType: HASH
- AttributeName: unixtime
KeyType: RANGE
AttributeDefinitions:
- AttributeName: player
AttributeType: S
- AttributeName: unixtime
AttributeType: N
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
最後にServerless Commandでデプロイします。
$ sls deploy
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3 (1.81 KB)...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
......................
Serverless: Stack update finished...
Service Information
service: serverless-janken
stage: dev
region: ***
api keys:
None
endpoints:
POST - https://***.amazonaws.com/dev/jankens
GET - https://***.amazonaws.com/dev/jankens
functions:
serverless-janken-dev-playJanken: arn:aws:lambda:***:***:function:serverless-janken-dev-playJanken
serverless-janken-dev-listJankens: arn:aws:lambda:***:***:function:serverless-janken-dev-listJankens
以上でローカルで開発したServerless アプリケーションをAWSにデプロイができました。