概要
- AWS Lambda入門①(Node編)〜関数をデプロイして動かす〜の続編です
- 今回はLambdaからDynamoDBにアクセスしてデータを保存したり取得したりしてみます
- テーブルから全件取得、1件取得、1件登録の3つの関数を作成します
DynamoDBとは
- AWSが提供するマネージドなデータベースサービスです
- RDBとは異なりkey-value形式なドキュメントデータベースです
LambdaからDynamoDBにアクセスしてみる
DynamoDBの設定
- まずはDynamoDBを使うためにServerlesFrameworkの設定をします
DynamoDBのテーブル定義の設定
- DynamoDBはデータベースなのでテーブルの作成からはじめます
- これまでと同じようにこれもServerlessFrameworkの機能で行うことができます
- 今回はHelloテーブルを作ってみます
-
serverless.yml
を修正します- 見づらいのでデフォルトで記載されていたコメントアウトは全て削除しています
serverless.yml
service: sls-sample
# AWS周りの設定
provider:
name: aws
runtime: nodejs12.x
region: ap-northeast-1
stage: dev
environment:
DYNAMODB_TABLE: ${self:service}-${self:provider.stage}
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}*'
# Lambdaの設定
functions:
hello:
handler: handler.hello
# DynamoDBの設定
resources:
Resources:
Hello:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.environment.DYNAMODB_TABLE}-hello
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
- 大きく分けて前段と後段の2つの定義を追加しています
serverless.yml
environment:
DYNAMODB_TABLE: ${self:service}-${self:provider.stage}
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}*'
- environmentはファイル内で扱える変数のような感じです
- テーブル名につけるprefixが何度か登場することになるのでenvironmentに定義しています
-
self:service
は一行目のservice
の値でself:provider.stage
は7行目あたりのstage
の値
- iamRoleStatementsはLambdaを実行するユーザの権限にDynamoDBへのアクセス許可を追加しています
serverless.yml
resources:
Resources:
Hello:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.environment.DYNAMODB_TABLE}-hello
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
- テーブルの定義をしています
- テーブル名は上で定義したprefixにつなげてhelloという名前にしています
テーブルを作成する
-
serverless.yml
に設定を追加した状態でデプロイすると自動でテーブルが作成されます
sls deploy
- Webコンソールにアクセスしてテーブルができていることを確認してみましょう
DynamoDBへのアクセス処理の実装
ライブラリの追加
- DynamoDBにアクセスするために必要なライブラリを追加します
npm i -D aws-sdk
# or
yarn add -D aws-sdk
DynamoDBへの接続処理
- 今回は以下の3つの処理を実装しようと思います
- 全量取得
- IDで検索して1件取得
- 1件登録
- 全部一気にやると量が多いのでまずは全量取得からいきます
-
handler.js
を修正します
handler.js
'use strict';
// AWS SDKをimport
const AWS = require('aws-sdk');
// DynamoDBにアクセスするためのクライアントの初期化
const dynamo = new AWS.DynamoDB.DocumentClient();
// 環境変数からテーブル名を取得(あとでserverless.ymlに設定します)
const tableName = process.env.tableName;
// 全量取得
module.exports.getAll = async () => {
const params = {
TableName: tableName,
};
try {
// DynamoDBにscanでアクセス
const result = await dynamo.scan(params).promise();
// 正常に取得できたらその値を返す
return {
statusCode: 200,
body: JSON.stringify(result.Items),
};
} catch (error) {
// エラーが発生したらエラー情報を返す
return {
statusCode: error.statusCode,
body: error.message,
};
}
};
module.exports.hello = async event => {
return {
statusCode: 200,
body: JSON.stringify({ message: event }),
};
};
- 説明はだいたいコードのコメントに書いておきました
- 今回はテーブルの内容を全量取得するので
.scan()
を使いましたがDynamoDB Clientは以下のようなAPIを提供します
- 複数件取得
- scan: 全件取得
- query: 条件に該当した項目を全件取得
- 単項目操作
- get: 1件取得
- put: 1件置換
- update: 1件部分更新
- delete: 1件削除
- 新しく
module.export
を追加したのでserverless.yml
も修正します- functionの項目を修正します
serverless.yml
# ...省略
functions:
hello:
handler: handler.hello
getAll:
handler: handler.getAll
environment:
tableName: ${self:provider.environment.DYNAMODB_TABLE}-hello
# ...省略
-
handler.js
でmodule.export.getAll
としたのでhandler: handler.getAll
を追加しました -
environment
はhandler.js
に対して環境変数としてテーブル名を渡しています
ローカルで動作確認
- Lambdaのコードができたので動かしてみます
- デプロイする前にローカルで動作確認しましょう
DynamoDBをローカルで動かすための設定
-
serverless-dynamodb-local
というDynamoDBをローカルで動かすライブラリがあるのでインストールします
yarn add -D serverless-dynamodb-local
- DynamoDBをローカルで動かすための設定も追加します
-
serverless.yml
の一番下に追加してください
serverless.yml
# ...省略
plugins:
- serverless-dynamodb-local
custom:
dynamodb:
stages: dev
start:
port: 8082
inMemory: true
migrate: true
seed: true
seed:
hello:
sources:
- table: ${self:provider.environment.DYNAMODB_TABLE}-hello
sources: [./seeds/hello.json]
-
plugins
は先程インストールしたserverless-dynamodb-local
を使うことを宣言しています -
custom.dynamodb
でローカルでDBを起動する時に使う設定をしています-
port
は何でも大丈夫です(デフォルトは8000) -
seed
は事前に登録しておくテストデータの設定です- 登録するデータは
./seeds/hello.json
に定義しておきます
- 登録するデータは
-
- テストデータとして
seeds/hello.json
を作成します
seeds/hello.json
[
{
"id": "1",
"message": "Hello"
},
{
"id": "2",
"message": "Hello!!!"
},
{
"id": "3",
"message": "Hello World"
}
]
- ServerlessFrameworkを使ってDBをインストールしセットアップを完了させます
sls dynamodb install
Lambda関数にローカルDB用の処理を追加
- ローカルのDBを使うために
handler.js
に少し手を加えます
handler.js
'use strict';
const AWS = require('aws-sdk');
// 環境変数にLOCALが設定されていたらローカルDB用の設定を使う(portはymlで定義したものを設定)
const options = process.env.LOCAL
? { region: 'localhost', endpoint: 'http://localhost:8082' }
: {};
const dynamo = new AWS.DynamoDB.DocumentClient(options);
// ...省略
ローカルでDBにアクセスする
- 準備が長くなりましたがいよいよアクセスしてみます
- まずはDBを起動します
sls dynamodb start
- 以下のようなログが出ればOKです
$ sls dynamodb start
Dynamodb Local Started, Visit: http://localhost:8082/shell
Serverless: DynamoDB - created table sls-sample-dev-hello
Seed running complete for table: sls-sample-dev-hello
- LambdaのgetAll関数を叩きます
-
LOCAL=true
は環境変数としてLOCALにtrueを設定しています(handler.jsでローカルのDBを見に行く判定で使っていたやつ)
-
LOCAL=true sls invoke local --function getAll
- 以下のようなログがでればOKです!
{
"statusCode": 200,
"body": "[{\"message\":\"Hello\",\"id\":\"1\"},{\"message\":\"Hello!\",\"id\":\"2\"},{\"message\":\"Hello World\",\"id\":\"3\"}]"
}
AWSにデプロイして動作確認
- ローカルで確認できたらAWSにデプロイしましょう
sls deploy
- コマンド一発でデプロイできて便利ですね
- serverlessコマンドでアクセスしてみましょう
sls invoke --function getAll
- 現状データがないのでデータは0件ですが200が返ってきていれば成功です
{
"statusCode": 200,
"body": "[]"
}
- この時点でデータがとれることを確認したい人はWebコンソール上でデータを追加した上で叩いてみてください
- あとでもいい人は次でデータ登録処理も追加するのでそのあとに取得できることは確認できます
残りの関数を追加する
- 同じ要領で1件取得と1件登録の処理を追加してみましょう
関数と設定の追加
-
handler.js
の一番下に関数を追加する
handler.js
// ...省略
// 1件取得
module.exports.get = async event => {
// パラメータで渡されたidを取得
const { id } = event;
// 検索条件のidを指定
const params = {
TableName: tableName,
Key: { id },
};
try {
const result = await dynamo.get(params).promise();
return {
statusCode: 200,
body: JSON.stringify(result.Item),
};
} catch (error) {
return {
statusCode: error.statusCode,
body: error.message,
};
}
};
// 1件登録
module.exports.put = async event => {
// 一意な値を作るためにタイムスタンプを取得
const id = String(Date.now());
const { message } = event;
const params = {
TableName: tableName,
Item: { id, message },
};
try {
const result = await dynamo.put(params).promise();
return {
statusCode: 200,
body: JSON.stringify(result),
};
} catch (error) {
return {
statusCode: error.statusCode,
body: error.message,
};
}
};
- 全件取得の時は
.scan()
を使いましたが1件取得は.get()
1件登録はput()
を使っています -
serverless.yml
のfunctionの項目に設定を追加します
servreless.yml
# ...省略
functions:
hello:
handler: handler.hello
getAll:
handler: handler.getAll
environment:
tableName: ${self:provider.environment.DYNAMODB_TABLE}-hello
get:
handler: handler.get
environment:
tableName: ${self:provider.environment.DYNAMODB_TABLE}-hello
put:
handler: handler.put
environment:
tableName: ${self:provider.environment.DYNAMODB_TABLE}-hello
ローカルで動作確認
- 1件登録
LOCAL=true sls invoke local --function put --data '{"message": "Hello!!!!!"}'
- 以下のようなログが出ればOK
{
"statusCode": 200,
"body": "{\"id\":\"1583340674414\",\"message\":\"Hello!!!!!\"}"
}
- 1件取得
LOCAL=true sls invoke local --function get --data '{"id": "1"}'
- 以下のようなログが出ればOK
{
"statusCode": 200,
"body": "{\"message\":\"Hello\",\"id\":\"1\"}"
}
AWSで動作確認
- ServerlessFrameworkのコマンドでデプロイします
sls deploy
- 1件登録
sls invoke --function put --data '{"message": "Hello!!!!!"}'
- 以下のようなログが出ればOK
{
"statusCode": 200,
"body": "{\"id\":\"1583340839287\",\"message\":\"Hello!!!!!\"}"
}
- 1件取得
- 直前の1件登録で追加したデータのIDを指定してみましょう
sls invoke --function get --data '{"id": "1583340839287"}'
- 以下のようなログが出ればOK
{
"statusCode": 200,
"body": "{\"id\":\"1583340839287\",\"message\":\"Hello!!!!!\"}"
}
おまけ
- 今回はscanとgetとputを作成しましたがquery(検索条件を指定して複数件取得)の場合は以下のような感じになります
- パラメータで指定したIDに合致するレコードが複数ある場合に全部取得できるような感じです
module.exports.query = async event => {
const { id } = event;
const params = {
TableName: tableName,
KeyConditionExpression: 'id = :id',
ExpressionAttributeValues: { ':id': id },
};
try {
const result = await dynamo.query(params).promise();
return {
statusCode: 200,
body: JSON.stringify(result.Items),
};
} catch (error) {
return {
statusCode: error.statusCode,
body: error.message,
};
}
};
感想
- LambdaからDynamoDBへのアクセスはDynamoDB Clientを使うと扱いやすいですね
- ServerlessFrameworkや周辺ライブラリを使うとローカルでも動作確認できるので環境周りもとても充実しています
- けっこう長くなってしまいましたがここまで読んでいただきありがとうございました