Edited at

Serverless アプリケーションをローカルで開発する

More than 1 year has passed since last update.

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 に設定を入れます。


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]

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でもローカルでも動作が可能なように、 event.isOffline を見て接続するDynamoDBを切り替えています。


hander.js

"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 ("isOffline" in event && event.isOffline) {
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に保存


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

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 に追記して下記のようにします。


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にデプロイができました。