
AWSのLambdaを筆頭にサーバーレスアプリケーションが注目を集めています。その最大の利点は稼働しているサーバーの管理から解放されることです。しかも、安い。今回はServerless Frameworkを用いてサーバーレスなCRUDアプリケーションを作ってみましょう。
サーバーレスアーキテクチャとは?
サーバーレスアーキテクチャとは、AWSなどのプロバイダーにお金を払って、開発者が意識する必要のないサーバー(マネージドサービス)を手に入れ、それを使ったアプリケーションの設計の事です。
AWSなどのIaaSが出る以前は、物理サーバーを調達してインフラを構築し、その上にOSやミドルウェアをインストールし、やっとその上にアプリケーションを乗せて稼働させていました。お金がある企業は自社のデータセンターを持っていた時代です。それが、AWSの登場により、物理サーバーは不要となり、IaaSのプロバイダーからEC2インスタンスのようなサーバーのリソースを購入する形が主流となりました。そして、AWSのLambdaの登場により、サーバーのリソースを購入する必要が無くなり、ついには関数というアプリケーションの実行費用だけを払えば良くなりました。そのコストも非常に安価です。サーバーの管理から解放されるだけではなく、コスト削減にもなるのです。今後はサーバーレスアーキテクチャが主流になることは誰の目にも明らかです。
今回は、サーバーレスなアプリケーションをより簡単に実装できるServerless Frameworkを用いてTODOをCRUDする簡単なAPIアプリケーションを実装してみましょう。
今回のアプリケーションのアーキテクチャ
今回作るアプリケーションのアーキテクチャは以下です。
サーバーレスアーキテクチャとしては基本的な構成ですが、API Gateway経由でLambdaの関数を呼び出し、DyanomoDBにデータを格納する構成です。Serverless Frameworkを使えばこれらがコマンドラインから楽々作成できます。
開発環境構築
それでは、事前準備から始めましょう。
前提条件
このチュートリアルを楽しむには以下の準備が必要です。
- AWSのアカウント登録が完了している
- Homebrewがインストール済みである
- NodeJSがインストール済みである
細かいバージョンは「環境」を参照して下さい。
必要なCLIのインストール
Homebrewよりawscliをインストールします。(NPMよりaws-cliをグローバルでインストールしても良いです)
$ brew install awscli
$ aws --version
aws-cli/1.15.0 Python/3.6.5 Darwin/17.5.0 botocore/1.10.0
NPMよりServerless FrameworkのCLIをインストールします。
$ npm install -g serverless
$ sls --version
1.26.1
パッケージ管理はYarnを使うので、入れていない人はインストールしましょう。
$ npm install -g yarn
$ yarn --version
1.6.0
IAMのグループとユーザを作成する
一応、今回用にIAMのグループとユーザを作っておきましょう。すでに使えるユーザがある場合は作る必要はありません。
AWSコンソールにアクセスして、IMAの画面を開きます。
まずはグループを作ります。メニューから「グループ」を選んで、「新しいグループの作成」をクリックします。
任意のグループ名を入力して、「次のステップ」をクリックします。
以下のポリシーを入力して、「次のステップ」をクリックします。
- PowerUserAccess
- IAMFullAccess
後は「グループ作成」をクリックすればグループが作成されます。
グループ画面から「todos-developers」グループに適切なアクセス許可が設定されている事が確認できます。
次にユーザを作ります。メニューから「ユーザー」を選び、「ユーザーを追加」をクリックします。
任意のユーザ名を入力して、「次のステップ:アクセス権限」をクリックします。
先程作ったグループを選択して、「次のステップ:確認」をクリックします。
ユーザ名と所属しているグループを確認したら、「ユーザーの作成」をクリックします。
これで、グループの作成とユーザの作成が完了しました。
Profileを設定する
コマンドラインから実行できるように、aws-cliを使って、作成したユーザの権限を設定します。
$ aws configure
AWS Access Key ID [****************OWKA]: XXXXXXXXXXXXXXXXXXXX
AWS Secret Access Key [****************KTLq]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Default region name [us-east-1]: us-east-1
Default output format [json]: json
$ cat ~/.aws/credentials
[default]
aws_access_key_id = XXXXXXXXXXXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
リージョンは普通は東京(ap-northeast-1)リージョンを選ぶと思いますが、チュートリアルなのでどこでも構いません。例として「バージニア北部(us-east-1)」で進めます。
Serverless Framework用の権限(profile)も設定します。
$ sls config credentials --provider aws --key YYYYYYYYYYYYYYYYYYYY --secret YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY --profile serverless-dev
Serverless: Setting up AWS...
Serverless: Saving your AWS profile in "~/.aws/credentials"...
Serverless: Success! Your AWS access keys were stored under the "serverless-dev" profile.
$ cat ~/.aws/credentials
[default]
aws_access_key_id = XXXXXXXXXXXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[serverless-dev]
aws_access_key_id = YYYYYYYYYYYYYYYYYYYY
aws_secret_access_key = YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
今回は同じIAMアカウントを使って問題ありませんが、実際に使う場合は権限を使い分けましょう。
これで事前準備は完了です。
TODOをCRUDするの簡単なAPIアプリケーションを作る
それではサクサクいきましょう。
プロジェクトの準備
今回のプロジェクト用のフォルダ、ファイル、パッケージを準備しましょう。
$ mkdir serverless-todos
$ cd serverless-todos
$ sls create -t aws-nodejs-ecma-script
$ yarn install
$ tree -L 1
.
├── first.js
├── node_modules
├── package.json
├── second.js
├── serverless.yml
├── webpack.config.js
└── yarn.lock
$ rm first.js second.js
$ yarn add --dev serverless-offline serverless-dynamodb-local
$ mkdir api models utils
$ touch api/todoHandlers.js
$ touch models/Todo.js
$ touch utils/dynamoose.js
$ yarn add dynamoose uuid
dynamoose.jsを実装する
今回はORマッパーとしてDynamooseを使います。これは名前の通り、MongooseにインスパイアーされたDynamoDB用のORマッパーです。なので、NodeJSに馴染みのある人なら比較的使いやすい仕様になっています。
まずは、Profileを設定する共通ユーティリティを作りましょう。
import dynamoose from 'dynamoose';
if (process.env.NODE_ENV === 'production') {
dynamoose.AWS.config.update({
accessKeyId: 'YYYYYYYYYYYYYYYYYYYY',
secretAccessKey: 'YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY',
region: 'us-east-1'
});
} else {
dynamoose.AWS.config.update({
region: 'localhost'
});
dynamoose.local('http://localhost:8000');
}
export default dynamoose;
より実用的には環境変数はプロパティに切り出すと良いでしょう。
Todo.jsを実装する
次に、モデルを作りましょう。
import uuid from 'uuid';
import dynamoose from '../utils/dynamoose';
const { Schema } = dynamoose;
const todoSchema = new Schema(
{
id: {
type: String,
hashKey: true,
default: () => uuid.v4()
},
task: {
type: String,
required: true
},
completed: {
type: Boolean,
default: false
}
},
{
timestamps: true
}
);
const Todo = dynamoose.model('todos', todoSchema);
export default Todo;
todoHandlers.jsを実装する
Lambda関数はHandlerとして実装しまましょう。
import Todo from '../models/Todo';
export const getTodos = (event, context, callback) => {
Todo.scan()
.exec()
.then(todos =>
callback(null, {
statusCode: 200,
body: JSON.stringify(todos)
})
)
.catch(err =>
callback(null, {
statusCode: 500,
body: JSON.stringify({
error: err
})
})
);
};
export const getTodo = (event, context, callback) => {
const { id } = event.pathParameters;
Todo.get(id)
.then(
todo =>
todo
? callback(null, {
statusCode: 200,
body: JSON.stringify(todo)
})
: callback(null, {
statusCode: 404
})
)
.catch(err =>
callback(null, {
statusCode: 500,
body: JSON.stringify({
error: err
})
})
);
};
export const addTodo = (event, context, callback) => {
const body = JSON.parse(event.body);
const { task } = body;
if (!task) {
return callback(null, {
statusCode: 400,
body: JSON.stringify({
error: `The property "todo" is required.`
})
});
}
const newTodo = new Todo({ task });
newTodo
.save({ overwrite: false })
.then(addedTodo =>
callback(null, {
statusCode: 200,
body: JSON.stringify(addedTodo)
})
)
.catch(err =>
callback(null, {
statusCode: 500,
body: JSON.stringify({
error: err
})
})
);
};
export const removeTodo = (event, context, callback) => {
const { id } = event.pathParameters;
Todo.delete({ id })
.then(() =>
callback(null, {
statusCode: 204
})
)
.catch(err =>
callback(null, {
statusCode: 500,
body: JSON.stringify({
error: err
})
})
);
};
export const updateTodo = (event, context, callback) => {
const { id } = event.pathParameters;
const { task, completed } = JSON.parse(event.body);
let todo = {};
if (task) todo.task = task;
if (completed) todo.completed = completed;
Todo.update({ id }, { $PUT: todo })
.then(updatedTodo =>
callback(null, {
statusCode: 200,
body: JSON.stringify(updatedTodo)
})
)
.catch(err =>
callback(null, {
statusCode: 500,
body: JSON.stringify({
error: err
})
})
);
};
serverless.ymlを設定する
「serverless.yml」はServerless Framework用の設定ファイルです。以下のように設定しましょう。
service:
name: serverless-todos
plugins:
- serverless-webpack
- serverless-dynamodb-local
- serverless-offline
provider:
name: aws
runtime: nodejs8.10
profile: serverless-dev
stage: ${opt:stage, 'dev'}
region: ${opt:region, 'us-east-1'}
apiKeys:
- myApiKey
environment:
NODE_ENV: ${opt:env, 'development'}
DYNAMODB_TABLE: todos
iamRoleStatements:
- Effect: "Allow"
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: "arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"
package:
include:
- api/**
- models/**
- utils/**
functions:
getTodos:
handler: api/todoHandlers.getTodos
events:
- http:
method: get
path: todos
private: true
getTodo:
handler: api/todoHandlers.getTodo
events:
- http:
path: todos/{id}
method: get
private: true
addTodo:
handler: api/todoHandlers.addTodo
events:
- http:
path: todos
method: post
private: true
removeTodo:
handler: api/todoHandlers.removeTodo
events:
- http:
path: todos/{id}
method: delete
private: true
updateTodo:
handler: api/todoHandlers.updateTodo
events:
- http:
path: todos/{id}
method: patch
private: true
resources:
Resources:
todosTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.environment.DYNAMODB_TABLE}
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
custom:
dynamodb:
start:
port: 8000
inMemory: true
migrate: true
seed: true
seed:
test:
sources:
- table: ${self:provider.environment.DYNAMODB_TABLE}
sources: [./seeds/fake-todos.json]
少し設定内容と使い方を説明します。
「profile」には今回作った「serverless-dev」を指定しています。省略すると「default」の権限を参照します。
「apiKeys」は、値を設定し、「functions」に「private: true」とすると、APIがプライベートになります。この意味は、APIを呼ぶ時にヘッダーに「x-api-key」が必要になります。今回は全てのAPIをプライベートにしています。
「package」は、「include」でビルドに含むディレクトリを記入します。逆に除外する場合は「exclude」を使用します。
「resources」にはAWS上に構築するDynamoDBとして「todosTable」を設定しています。
「custom」にはローカル実行用のDynamoDBの設定を記述しています。「inMemory: true」の場合は、ローカルで起動した時にデータがメモリに作成され、停止した時にデータは破棄されます。他にも「dbPath」や「sharedDb」を設定することで、ローカルにファイルとしてデータを残す事が可能です。「seed」にはテストデータのファイルを指定しています。
「${}」を使った設定は、optはコマンド引数、selfはこのファイル自体、envは環境変数を参照します。
ローカル環境でアプリケーションを実行する
テストデータを準備する
「serverless.yml」の「seed」に設定したテストデータを作成します。
$ mkdir seeds
$ touch seeds/fake-todos.json
「fake-todos.json」は以下のようにします。
[
{
"id": "3d1a0cd0-4aba-11e8-8541-13ccb3287721",
"task": "Have a coffee",
"completed": "false",
"createdAt": 1524902442626,
"updatedAt": 1524902442626
},
{
"id": "42750d60-4aba-11e8-b86e-b3202a0b01c8",
"task": "Go to work",
"completed": "false",
"createdAt": 1524902442626,
"updatedAt": 1524902442626
},
{
"id": "3eeaae20-4aba-11e8-8240-17e64d342d41",
"task": "Go out for dinner",
"completed": "false",
"createdAt": 1524902442626,
"updatedAt": 1524902442626
},
{
"id": "3f9a7620-4aba-11e8-a3c1-61aaaadad74f",
"task": "Get up early",
"completed": "true",
"createdAt": 1524902439922,
"updatedAt": 1524902439922
},
{
"id": "4025c630-4aba-11e8-ad79-5d5313a362c5",
"task": "Make a ham and lettuce sandwich",
"completed": "true",
"createdAt": 1524902439922,
"updatedAt": 1524902439922
}
]
DynamoDB Localを動作確認する
アプリケーションを起動する前にDynamoDB Localをインストールしましょう。
$ sls dynamodb install
Started downloading Dynamodb-local. Process may take few minutes.
...
Installation complete!
後でローカルのデータベースを削除する場合は「sls dynamodb remove」を実行すればできます。
次にオフラインでAPIを起動してみましょう。
$ sls offline start
...
Serverless: DynamoDB - created table todos
Seed running complete for table: todos
Serverless: Starting Offline: dev/us-east-1.
Serverless: Key with token: d41d8cd98f00b204e9800998ecf8427e
Serverless: Remember to use x-api-key on the request headers
Serverless: Routes for getTodos:
Serverless: GET /todos
Serverless: Routes for getTodo:
Serverless: GET /todos/{id}
Serverless: Routes for addTodo:
Serverless: POST /todos
Serverless: Routes for removeTodo:
Serverless: DELETE /todos/{id}
Serverless: Routes for updateTodo:
Serverless: PATCH /todos/{id}
Serverless: Offline listening on http://localhost:3000
「http://localhost:8000/shell/」にアクセスします。
左側のパネルにDynamoDB Javascript Shellを入力することで実行できます。今回のアプリケーションのテーブル名は「todos」でした。テーブル内のデータを全て表示する以下のスクリプトを入力して実行してみましょう。
dynamodb.scan({ TableName: 'todos' }, (err, todos) => {
if (err) return ppJson(err);
ppJson(todos);
});
テストデータが全て格納されている事が分かります。
GET /todos
ヘッダーに設定する「x-api-key」に設定する値は、起動時に表示された「token: d41d8cd98f00b204e9800998ecf8427e」を使います。
$ curl -H 'x-api-key:d41d8cd98f00b204e9800998ecf8427e' http://localhost:3000/todos | jq
...
[
{
"createdAt": "2018-04-28T08:00:39.922Z",
"id": "3f9a7620-4aba-11e8-a3c1-61aaaadad74f",
"task": "Get up early",
"completed": true,
"updatedAt": "2018-04-28T08:00:39.922Z"
},
{
"createdAt": "2018-04-28T08:00:39.922Z",
"id": "4025c630-4aba-11e8-ad79-5d5313a362c5",
"task": "Make a ham and lettuce sandwich",
"completed": true,
"updatedAt": "2018-04-28T08:00:39.922Z"
},
{
"createdAt": "2018-04-28T08:00:42.626Z",
"id": "3eeaae20-4aba-11e8-8240-17e64d342d41",
"task": "Go out for dinner",
"completed": false,
"updatedAt": "2018-04-28T08:00:42.626Z"
},
{
"createdAt": "2018-04-28T08:00:42.626Z",
"id": "42750d60-4aba-11e8-b86e-b3202a0b01c8",
"task": "Go to work",
"completed": false,
"updatedAt": "2018-04-28T08:00:42.626Z"
},
{
"createdAt": "2018-04-28T08:00:42.626Z",
"id": "3d1a0cd0-4aba-11e8-8541-13ccb3287721",
"task": "Have a coffee",
"completed": false,
"updatedAt": "2018-04-28T08:00:42.626Z"
}
]
GET /todos/{id}
$ curl -H 'x-api-key:d41d8cd98f00b204e9800998ecf8427e' http://localhost:3000/todos/3d1a0cd0-4aba-11e8-8541-13ccb3287721 | jq
...
{
"createdAt": "2018-04-28T08:00:42.626Z",
"id": "3d1a0cd0-4aba-11e8-8541-13ccb3287721",
"task": "Have a coffee",
"completed": false,
"updatedAt": "2018-04-28T08:00:42.626Z"
}
POST /todos
$ curl -X POST -H 'Content-Type:application/json' -H 'x-api-key:d41d8cd98f00b204e9800998ecf8427e' -d '{"task":"Add new todo"}' http://localhost:3000/todos | jq
...
{
"message": "Added the todo of id:936407b0-4ac1-11e8-a4ba-c554c49eaa00",
"addedTodo": {
"id": "936407b0-4ac1-11e8-a4ba-c554c49eaa00",
"task": "Add new todo",
"completed": false,
"createdAt": "2018-04-28T08:53:09.676Z",
"updatedAt": "2018-04-28T08:53:09.676Z"
}
}
$ curl -H 'x-api-key:d41d8cd98f00b204e9800998ecf8427e' http://localhost:3000/todos | jq
...
[
{
"createdAt": "2018-04-28T08:00:39.922Z",
"id": "3f9a7620-4aba-11e8-a3c1-61aaaadad74f",
"task": "Get up early",
"completed": true,
"updatedAt": "2018-04-28T08:00:39.922Z"
},
{
"createdAt": "2018-04-28T08:00:39.922Z",
"id": "4025c630-4aba-11e8-ad79-5d5313a362c5",
"task": "Make a ham and lettuce sandwich",
"completed": true,
"updatedAt": "2018-04-28T08:00:39.922Z"
},
{
"createdAt": "2018-04-28T08:00:42.626Z",
"id": "3eeaae20-4aba-11e8-8240-17e64d342d41",
"task": "Go out for dinner",
"completed": false,
"updatedAt": "2018-04-28T08:00:42.626Z"
},
{
"createdAt": "2018-04-28T08:53:09.676Z",
"id": "936407b0-4ac1-11e8-a4ba-c554c49eaa00",
"task": "Add new todo",
"completed": false,
"updatedAt": "2018-04-28T08:53:09.676Z"
},
{
"createdAt": "2018-04-28T08:00:42.626Z",
"id": "42750d60-4aba-11e8-b86e-b3202a0b01c8",
"task": "Go to work",
"completed": false,
"updatedAt": "2018-04-28T08:00:42.626Z"
},
{
"createdAt": "2018-04-28T08:00:42.626Z",
"id": "3d1a0cd0-4aba-11e8-8541-13ccb3287721",
"task": "Have a coffee",
"completed": false,
"updatedAt": "2018-04-28T08:00:42.626Z"
}
]
DELETE /todos/{id}
$ curl -v -X DELETE -H 'x-api-key:d41d8cd98f00b204e9800998ecf8427e' http://localhost:3000/todos/3f9a7620-4aba-11e8-a3c1-61aaaadad74f
...
< HTTP/1.1 204 No Content
...
$ curl -v -X DELETE -H 'x-api-key:d41d8cd98f00b204e9800998ecf8427e' http://localhost:3000/todos/4025c630-4aba-11e8-ad79-5d5313a362c5 | jq
...
< HTTP/1.1 204 No Content
...
$ curl -H 'x-api-key:d41d8cd98f00b204e9800998ecf8427e' http://localhost:3000/todos | jq
...
[
{
"createdAt": "2018-04-28T08:00:42.626Z",
"id": "3eeaae20-4aba-11e8-8240-17e64d342d41",
"task": "Go out for dinner",
"completed": false,
"updatedAt": "2018-04-28T08:00:42.626Z"
},
{
"createdAt": "2018-04-28T08:53:09.676Z",
"id": "936407b0-4ac1-11e8-a4ba-c554c49eaa00",
"task": "Add new todo",
"completed": false,
"updatedAt": "2018-04-28T08:53:09.676Z"
},
{
"createdAt": "2018-04-28T08:00:42.626Z",
"id": "42750d60-4aba-11e8-b86e-b3202a0b01c8",
"task": "Go to work",
"completed": false,
"updatedAt": "2018-04-28T08:00:42.626Z"
},
{
"createdAt": "2018-04-28T08:00:42.626Z",
"id": "3d1a0cd0-4aba-11e8-8541-13ccb3287721",
"task": "Have a coffee",
"completed": false,
"updatedAt": "2018-04-28T08:00:42.626Z"
}
]
PATCH /todos/{id}
$ curl -X PATCH -H 'Content-Type:application/json' -H 'x-api-key:d41d8cd98f00b204e9800998ecf8427e' -d '{"completed":true}' http://localhost:3000/todos/3eeaae20-4aba-11e8-8240-17e64d342d41 | jq
...
{
"createdAt": "2018-04-28T08:00:42.626Z",
"completed": true,
"id": "3eeaae20-4aba-11e8-8240-17e64d342d41",
"task": "Add new todo",
"updatedAt": "2018-04-28T09:07:34.272Z"
}
$ curl -X PATCH -H 'Content-Type:application/json' -H 'x-api-key:d41d8cd98f00b204e9800998ecf8427e' -d '{"completed":true}' http://localhost:3000/todos/936407b0-4ac1-11e8-a4ba-c554c49eaa00 | jq
...
{
"createdAt": "2018-04-28T08:53:09.676Z",
"completed": true,
"id": "936407b0-4ac1-11e8-a4ba-c554c49eaa00",
"task": "Go out for dinner",
"updatedAt": "2018-04-28T09:07:55.426Z"
}
$ curl -H 'x-api-key:d41d8cd98f00b204e9800998ecf8427e' http://localhost:3000/todos | jq
...
[
{
"createdAt": "2018-04-28T08:00:42.626Z",
"completed": true,
"id": "3eeaae20-4aba-11e8-8240-17e64d342d41",
"task": "Go out for dinner",
"updatedAt": "2018-04-28T09:07:34.272Z"
},
{
"createdAt": "2018-04-28T08:53:09.676Z",
"completed": true,
"id": "936407b0-4ac1-11e8-a4ba-c554c49eaa00",
"task": "Add new todo",
"updatedAt": "2018-04-28T09:07:55.426Z"
},
{
"createdAt": "2018-04-28T08:00:42.626Z",
"id": "42750d60-4aba-11e8-b86e-b3202a0b01c8",
"task": "Go to work",
"completed": false,
"updatedAt": "2018-04-28T08:00:42.626Z"
},
{
"createdAt": "2018-04-28T08:00:42.626Z",
"id": "3d1a0cd0-4aba-11e8-8541-13ccb3287721",
"task": "Have a coffee",
"completed": false,
"updatedAt": "2018-04-28T08:00:42.626Z"
}
]
ローカルで正しく動くことが確認できました。Control+Cでローカルで起動しているアプリケーションを停止しましょう。
また、このブログに載せているソースコードを転写する場合は基本的には動くはずですが、環境差分などですぐに動かない場合もありえます。その場合は先に次の章の「AWS上でアプリケーションを実行する」に進んで、後で戻ってデバッグすると理解が進むと思います。
AWS上でアプリケーションを実行する
いよいよ、AWS上にデプロイして実行しましょう。
AWSにデプロイする
$ sls deploy -s prod --env production
...
Service Information
service: serverless-todos
stage: prod
region: us-east-1
stack: serverless-todos-prod
api keys:
myApiKey: fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf
endpoints:
GET - https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos
GET - https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos/{id}
POST - https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos
DELETE - https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos/{id}
PATCH - https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos/{id}
functions:
getTodos: serverless-todos-prod-getTodos
getTodo: serverless-todos-prod-getTodo
addTodo: serverless-todos-prod-addTodo
removeTodo: serverless-todos-prod-removeTodo
updateTodo: serverless-todos-prod-updateTodo
AWS上にちゃんと作成されているか確認しましょう。
DynamoDBの画面を見ると、「todos」テーブルが作成されています。
AWS Lambdaの画面を見ると、5つの関数が作成されています。
関数を選択すると、以下のようにAPI Gateway、Lambda、DynamoDB、Cloudwatch Logsが自動的に作成されています。
これが自動ですからお手軽ですね。
POST /todos
AWS上のDynamoDBは空なのでPOSTからいきましょう。
$ curl -X POST -H 'Content-Type:application/json' -H 'x-api-key:fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf' -d '{"task":"Add new todo 1"}' https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos | jq
...
{
"message": "Added the todo of id:34eaa6d0-4acb-11e8-81a8-e5b9cc57316e",
"addedTodo": {
"id": "34eaa6d0-4acb-11e8-81a8-e5b9cc57316e",
"task": "Add new todo 1",
"completed": false,
"createdAt": "2018-04-28T10:02:06.141Z",
"updatedAt": "2018-04-28T10:02:06.141Z"
}
}
$ curl -X POST -H 'Content-Type:application/json' -H 'x-api-key:fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf' -d '{"task":"Add new todo 2"}' https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos | jq
...
{
"message": "Added the todo of id:3b3b03e0-4acb-11e8-81a8-e5b9cc57316e",
"addedTodo": {
"id": "3b3b03e0-4acb-11e8-81a8-e5b9cc57316e",
"task": "Add new todo 2",
"completed": false,
"createdAt": "2018-04-28T10:02:16.734Z",
"updatedAt": "2018-04-28T10:02:16.734Z"
}
}
$ curl -X POST -H 'Content-Type:application/json' -H 'x-api-key:fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf' -d '{"task":"Add new todo 3"}' https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos | jq
...
{
"message": "Added the todo of id:41b50900-4acb-11e8-81a8-e5b9cc57316e",
"addedTodo": {
"id": "41b50900-4acb-11e8-81a8-e5b9cc57316e",
"task": "Add new todo 3",
"completed": false,
"createdAt": "2018-04-28T10:02:27.600Z",
"updatedAt": "2018-04-28T10:02:27.600Z"
}
}
$ curl -X POST -H 'Content-Type:application/json' -H 'x-api-key:fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf' -d '{"task":"Add new todo 4"}' https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos | jq
...
{
"message": "Added the todo of id:7e6365e0-4acb-11e8-81a8-e5b9cc57316e",
"addedTodo": {
"id": "7e6365e0-4acb-11e8-81a8-e5b9cc57316e",
"task": "Add new todo 4",
"completed": false,
"createdAt": "2018-04-28T10:04:09.407Z",
"updatedAt": "2018-04-28T10:04:09.407Z"
}
}
$ curl -X POST -H 'Content-Type:application/json' -H 'x-api-key:fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf' -d '{"task":"Add new todo 5"}' https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos | jq
...
{
"message": "Added the todo of id:842e6650-4acb-11e8-81a8-e5b9cc57316e",
"addedTodo": {
"id": "842e6650-4acb-11e8-81a8-e5b9cc57316e",
"task": "Add new todo 5",
"completed": false,
"createdAt": "2018-04-28T10:04:19.125Z",
"updatedAt": "2018-04-28T10:04:19.125Z"
}
}
GET /todos
$ curl -H 'x-api-key:fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf' https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos | jq
...
[
{
"completed": false,
"createdAt": "2018-04-28T10:02:06.141Z",
"task": "Add new todo 1",
"id": "34eaa6d0-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:02:06.141Z"
},
{
"completed": false,
"createdAt": "2018-04-28T10:04:09.407Z",
"task": "Add new todo 4",
"id": "7e6365e0-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:04:09.407Z"
},
{
"completed": false,
"createdAt": "2018-04-28T10:02:27.600Z",
"task": "Add new todo 3",
"id": "41b50900-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:02:27.600Z"
},
{
"completed": false,
"createdAt": "2018-04-28T10:04:19.125Z",
"task": "Add new todo 5",
"id": "842e6650-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:04:19.125Z"
},
{
"completed": false,
"createdAt": "2018-04-28T10:02:16.734Z",
"task": "Add new todo 2",
"id": "3b3b03e0-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:02:16.734Z"
}
]
AWSのDynamoDBの「todos」テーブルのデータも確認してみましょう。
POSTしたデータが入っていることが確認できます。
GET /todos/{id}
3番目に入れたデータを確認してみましょう。
$ curl -H 'x-api-key:fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf' https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos/41b50900-4acb-11e8-81a8-e5b9cc57316e | jq
...
{
"completed": false,
"createdAt": "2018-04-28T10:02:27.600Z",
"task": "Add new todo 3",
"id": "41b50900-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:02:27.600Z"
}
合っていますね。
DELETE /todos/{id}
4番目のデータを削除してみましょう。
$ curl -v -X DELETE -H 'x-api-key:fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf' https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos/7e6365e0-4acb-11e8-81a8-e5b9cc57316e
...
< HTTP/1.1 204 No Content
...
$ curl -H 'x-api-key:fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf' https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos | jq
...
[
{
"completed": false,
"createdAt": "2018-04-28T10:02:06.141Z",
"task": "Add new todo 1",
"id": "34eaa6d0-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:02:06.141Z"
},
{
"completed": false,
"createdAt": "2018-04-28T10:02:27.600Z",
"task": "Add new todo 3",
"id": "41b50900-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:02:27.600Z"
},
{
"completed": false,
"createdAt": "2018-04-28T10:04:19.125Z",
"task": "Add new todo 5",
"id": "842e6650-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:04:19.125Z"
},
{
"completed": false,
"createdAt": "2018-04-28T10:02:16.734Z",
"task": "Add new todo 2",
"id": "3b3b03e0-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:02:16.734Z"
}
]
正しく削除されています。
一応AWS上のデータも確認しておきましょう。
整合性は取れていますね。
PATCH /todos/{id}
1番目のデータと5番目のデータを更新しましょう。
$ curl -X PATCH -H 'Content-Type:application/json' -H 'x-api-key:fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf' -d '{"completed":true}' https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos/34eaa6d0-4acb-11e8-81a8-e5b9cc57316e | jq
...
{
"completed": true,
"createdAt": "2018-04-28T10:02:06.141Z",
"task": "Add new todo 1",
"id": "34eaa6d0-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:21:15.281Z"
}
$ curl -X PATCH -H 'Content-Type:application/json' -H 'x-api-key:fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf' -d '{"completed":true}' https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos/842e6650-4acb-11e8-81a8-e5b9cc57316e | jq
...
{
"completed": true,
"createdAt": "2018-04-28T10:04:19.125Z",
"task": "Add new todo 5",
"id": "842e6650-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:21:41.415Z"
}
$ curl -H 'x-api-key:fd23sKJTVJY42xKBfd8nczlvsfucBKYF2o3GKYxf' https://v08cawefr9.execute-api.us-east-1.amazonaws.com/prod/todos | jq
...
[
{
"completed": true,
"createdAt": "2018-04-28T10:02:06.141Z",
"task": "Add new todo 1",
"id": "34eaa6d0-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:21:15.281Z"
},
{
"completed": false,
"createdAt": "2018-04-28T10:02:27.600Z",
"task": "Add new todo 3",
"id": "41b50900-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:02:27.600Z"
},
{
"completed": true,
"createdAt": "2018-04-28T10:04:19.125Z",
"task": "Add new todo 5",
"id": "842e6650-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:21:41.415Z"
},
{
"completed": false,
"createdAt": "2018-04-28T10:02:16.734Z",
"task": "Add new todo 2",
"id": "3b3b03e0-4acb-11e8-81a8-e5b9cc57316e",
"updatedAt": "2018-04-28T10:02:16.734Z"
}
]
これも一応、DynamoDBも確認してみましょう。
OKですね。
AWSから削除する
これで動作確認は完了したので、不要になったアプリケーションを綺麗に削除しましょう。
$ sls remove -s prod
Serverless: Removing usage plan association...
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack removal progress...
.................................................................
Serverless: Stack removal finished...
AWS上に何も無いことを確認しましょう。
DynamoDBの画面には「todos」テーブルはありません。
Lambdaの画面には今回デプロイした関数はありません。
綺麗に削除されました。これでチュートリアルは終了です。
最後に
いかがでしたか?これでサーバーレスなアプリケーションを作れるようになった事でしょう。一度やってみると、案外簡単だと気づきますよね。マイグレーションでも新規開発でもサーバーレスアーキテクチャをどんどん採用していきましょう。では。
環境
- OS: macOS High Sierra 10.13.4
- Homebrew: 1.6.1
- NodeJS: v9.8.0
- Yarn: 1.6.0
- serverless: 1.26.1
- serverless-webpack: 3.1.1
- serverless-offline: 3.20.3
- serverless-dynamodb-local: 0.2.28
- dynamoose: 0.8.7
- uuid: 3.2.1