LoginSignup
23
16

More than 3 years have passed since last update.

AWS Lambda入門②(Node編)〜DynamoDBにアクセスする〜

Last updated at Posted at 2020-03-04

概要

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コンソールにアクセスしてテーブルができていることを確認してみましょう

スクリーンショット 2020-03-03 0.38.26.png

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.jsmodule.export.getAllとしたのでhandler: handler.getAllを追加しました
  • environmenthandler.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コンソール上でデータを追加した上で叩いてみてください
    • あとでもいい人は次でデータ登録処理も追加するのでそのあとに取得できることは確認できます

スクリーンショット 2020-03-05 1.35.36.png

残りの関数を追加する

  • 同じ要領で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や周辺ライブラリを使うとローカルでも動作確認できるので環境周りもとても充実しています
  • けっこう長くなってしまいましたがここまで読んでいただきありがとうございました
23
16
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
16