42
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Serverless Framework で DynamoDB を使う

Last updated at Posted at 2016-11-03

Serverless Frameworkで DynamoDB を使ってみます。

参考

ソースリポジトリ

ソースは以下のリポジトリを参照してください。
https://github.com/katsuhiko/sls-dynamodb

バージョン

version
node 6.9.1
npm 3.10.8
serverless framework 1.1.0

Lambda が Node.js V4.3 なので、合わせるべきかもしれません。今はローカルでは動かしていないので合わしていません。

DynamoDB を使う

todos.js

気軽に使いたいので、あまりレイヤは設けたくはないのですが、Modelに相当する部分のみを todos.js として切り出しました。

Lambda と DynamoDB を意識せずに作れると良いけど、がっつり DynamoDB を意識している作りです。

moment」は、日付を扱いやすくするライブラリです。
ユニークなキーを生成するのに「uuid」を使っています。「chance」でも良いと思います。

DynamoDB のテーブル名にステージを prefix として追加しています。やり方は他にもあるかもしれませんが、各ステージごとに DynamoDB テーブルを作る仕組みを盛り込まないと実際に利用するときに困ると思います。今回は、ステージを prefix としてテーブル名につけることで対応しました。

Javascript からステージを参照できるようにするために Plugin「serverless-plugin-write-env-vars」と「dotenv」を使っています。コメントによると Serverless Framework で似た機能が提供されるらしいので、いずれ不要になると思います。

'use strict';

const uuid = require('uuid'),
      moment = require('moment'),
      tableName = `${process.env.STAGE}-todos`;

module.exports.readAll = (db, callback) => {
  const params = {
    TableName: tableName
  };

  return db.scan(params, (err, data) => {
    if (err) {
      callback(err);
    } else {
      callback(err, data.Items);
    }
  });
};

module.exports.readOne = (db, id, callback) => {
  const params = {
    TableName: tableName,
    Key: {
      id: id
    }
  };

  return db.get(params, (err, data) => {
    if (err) {
      callback(err);
    } else {
      callback(err, data.Item);
    }
  });
};

module.exports.create = (db, data, callback) => {
  data.id = uuid.v1();
  data.updatedUtc = moment().utc().toISOString();

  const params = {
    TableName: tableName,
    Item: data
  };

  return db.put(params, (err, data) => {
    if (err) {
      callback(err);
    } else {
      callback(err, params.Item);
    }
  });
};

module.exports.update = (db, id, data, callback) => {
  data.id = id;
  data.updatedAt = moment().utc().toISOString();

  const params = {
    TableName : tableName,
    Item: data
  };

  return db.put(params, (err, data) => {
    if (err) {
      callback(err);
    } else {
      callback(err, params.Item);
    }
  });
};

module.exports.delete = (db, id, callback) => {
  const params = {
    TableName : tableName,
    Key: {
      id: id
    }
  };

  return db.delete(params, (err, data) => {
    if (err) {
      callback(err);
    } else {
      callback(err, params.Key);
    }
  });
};

handler.js

Lambda Proxy に対応した返却を行っています。context ではなく callback  を使っています。しっかりした情報を得ることはできませんでしたが、callback を使うのが良いのではないかと思います。

極力シンプルにしたいですが、HTTPステータスを返却するところがどうしても IF が入ってキレイにできなかったです。

'use strict';

const AWS = require('aws-sdk'),
      dynamoDb = new AWS.DynamoDB.DocumentClient(),
      env = require('dotenv').config(),
      todos = require('./todos.js');

const createResponse = (statusCode, body) => (
  {
    statusCode,
    headers: {
      'Access-Control-Allow-Origin': '*', // Required for CORS support to work
    },
    body: JSON.stringify(body),
  }
);

module.exports.todosReadAll = (event, context, callback) => {
  todos.readAll(dynamoDb, (err, result) => {
    if (err) {
      callback(createResponse(500, { message: err.message }));
    } else {
      callback(null, createResponse(200, result));
    }
  });
};

module.exports.todosReadOne = (event, context, callback) => {
  const id = event.pathParameters.id;

  todos.readOne(dynamoDb, id, (err, result) => {
    if (err) {
      callback(createResponse(500, { message: err.message }));
    }  else if (!result) {
      callback(null, createResponse(404, { message: 'not found'}));
    } else {
      callback(null, createResponse(200, result));
    }
  });
};

module.exports.todosCreate = (event, context, callback) => {
  const data = JSON.parse(event.body);

  todos.create(dynamoDb, data, (err, result) => {
    if (err) {
      callback(createResponse(500, { message: err.message }));
    } else {
      callback(null, createResponse(201, result));
    }
  });
};

module.exports.todosUpdate = (event, context, callback) => {
  const id = event.pathParameters.id,
        data = JSON.parse(event.body);

  todos.update(dynamoDb, id, data, (err, result) => {
    if (err) {
      callback(createResponse(500, { message: err.message }));
    } else {
      callback(null, createResponse(200, result));
    }
  });
};

module.exports.todosDelete = (event, context, callback) => {
  const id = event.pathParameters.id;

  todos.delete(dynamoDb, id, (err, result) => {
    if (err) {
      callback(createResponse(500, { message: err.message }));
    } else {
      callback(null, createResponse(204));
    }
  });
};

serverless.yml

iamRoleStatementsResources を使って DynamoDB へアクセスする権限とテーブル作成を行っています。

この設定で作られた DynamoDB テーブルは DeletionPolicy: Retain を指定しているので、sls remove を実行しても削除されません。
不要な場合、AWS Console 等を使って別途削除する必要があります。

service: sls-dynamodb

provider:
  name: aws
  runtime: nodejs4.3
  stage: ${opt:stage, self:custom.defaultStage}
  region: ${opt:region, self:custom.defaultRegion}
  profile: ${self:custom.profiles.${self:provider.stage}}

  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:DescribeTable
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: "arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.stage}-*"

custom:
  defaultStage: dev
  defaultRegion: ap-northeast-1
  profiles:
    dev: devSls
    prod: prodSls
  writeEnvVars:
    STAGE: ${self:provider.stage}

package:
  exclude:
    - .git/**
    - README.md
    - node_modules/serverless-plugin-write-env-vars/**

plugins:
  - serverless-plugin-write-env-vars

functions:
  todosReadAll:
    handler: handler.todosReadAll
    events:
      - http:
          path: todos
          method: get
          cors: true

  todosReadOne:
    handler: handler.todosReadOne
    events:
      - http:
          path: todos/{id}
          method: get
          cors: true

  todosCreate:
    handler: handler.todosCreate
    events:
      - http:
          path: todos
          method: post
          cors: true

  todosUpdate:
    handler: handler.todosUpdate
    events:
      - http:
          path: todos/{id}
          method: patch
          cors: true

  todosDelete:
    handler: handler.todosDelete
    events:
      - http:
          path: todos/{id}
          method: delete
          cors: true

resources:
  Resources:
    TodosDynamoDbTable:
      Type: 'AWS::DynamoDB::Table'
      DeletionPolicy: Retain
      Properties:
        AttributeDefinitions:
          -
            AttributeName: id
            AttributeType: S
        KeySchema:
          -
            AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        TableName: "${self:provider.stage}-todos"

サンプルを動かしてみる

開発を始めるための Serverless Framework のインストール等については「Serverless Framework で Hello World を作る」を参照してください。

サンプルのインストール/デプロイ

Serverless Framework のインストールと AWS への接続ができていたら、以下のコマンドでサンプルをインストール/デプロイできます。

serverless install --url https://github.com/katsuhiko/sls-dynamodb
cd sls-dynamodb
npm install
serverless deploy -v

実行

curl を使って実行します。「XXXX」部分は各環境に合わして変更してください。

Read all

curl https://XXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos

Read one

curl https://XXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos/<id>

Create

curl -X POST https://XXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos --data '{ "content" : "Learn Serverless" }'

Update

curl -X PATCH https://XXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos/<id> --data '{ "content" : "Understand Serverless" }'

Delete

curl -X DELETE https://XXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos/<id>

あと片付け

serverless remove -v

上記でも DynamoDB テーブルが残っているので、AWS Console 等から DynamoDB テーブルを削除します。

感想

DynamoDB を使うのは簡単でした。

気になった点は1点。

Lambda にデプロイされる zip の node_modules に plugin に関するソースも含まれる点です。(細かいですが。)
serverless.yml で package.exclude していますが、package.json の devDependencies は含めないようにする仕組みが欲しいと思いました。

42
35
1

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
42
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?