Top / AWS サーバレス APIGateway ▶ Lambda ▶ DyanamoDBを操作する Node.js

AWS サーバレス APIGateway ▶ Lambda ▶ DyanamoDBを操作する Node.js
January 13, 2020

DEVELOPMENT

web
js
aws
lambda
apigateway
dyanamoDB

サーバーレスでのアプリケーション実装時に、どうしてもデータの保存処理が必要なり、今回は色々ある中からDyanmoDBを選びました。その時、API Gateway から Lambdaを呼び、LambdaでDyanamoDBを操作し、データの取得・作成・更新・削除を実装してみました。

手順

AWSのコンソール上での操作がほとんどです。コールするAPIをAPIGateway上で設定し、Lambdaが呼び出され、DyanamoDBを操作する流れになります。

  1. Lambda関数で処理を書く(IAM設定)
  2. APIGatewayでCURDに沿って、各メソッドを定義する
  3. DyanamoDB上で、テーブルを作成する

前提条件

  • AWSのアカウントを持っている
  • APIを叩ける環境がある(jsからなど)

そのくらい

Lambdaの作成

  • まずは、LambdaからDynamoDBにアクセスできるようロールをIAMから作成しておきます。

    {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        },
        {
            "Action": [
                "dynamodb:Get*",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:BatchWrite*",
                "dynamodb:CreateTable",
                "dynamodb:Delete*",
                "dynamodb:Update*",
                "dynamodb:PutItem"
            ],
            "Effect": "Allow",
            "Resource": [
                "*"
            ]
        }
    ]
    }
  • AWSコンソールのLambdaから関数の作成をします。 lambda1
  • 実行ロールの選択または作成を展開し、先程作成したロールを選択します。 lambda2
  • 関数を下記を例にDynamoDB.DocumentClientを使用して実装していきます
  • 今回は、APIGatewayからLambdaへマッピングテンプレートにてデータを渡す際に、メソッドごとにlambda関数を作るのは無駄なので、どの処理をすべきかを判定するために、APIGatewayからメソッド名を渡して、lambda側でそのメソッド名に応じて処理を変更するという形にしました。 メソッド名は、eventのtypeで渡ってくるように後でAPIGateway側で設定します
let AWS = require('aws-sdk')
let documentClient = new AWS.DynamoDB.DocumentClient({
      apiVersion: '2012-08-10',
      region: "xx-xxxx-xx"
})
let tableName = 'xxxxxxxxxxxxxxx'

exports.handler = async(event) => {

    switch (event.type) {
        case 'POST':
            return await create(event)
        case 'PUT':
            return await update(event)
        case 'DELETE':
            return await destroy(event)
        default:
            return await get(event)
    }
}

// 取得
async function get(event) {

    const params = {
        TableName: tableName,
        IndexName: 'id',
        ExpressionAttributeNames:{'#key': 'id'},
        ExpressionAttributeValues:{':val': event.id},
        KeyConditionExpression: '#key = :val'
    }

    var result
    try {
        result = await documentClient.query(params).promise()
    }
    catch (e) {
        result = e
    }

    return {
        statusCode: 200,
        body: result,
    }
}

// 作成
async function create(event) {
    const params = {
        TableName: tableName,
        Key: {
            id: event.requestId,
        },
        UpdateExpression: 'set update_key=:update_key',
        ExpressionAttributeValues: {
            ':update_key': event.email, // ここはDynamoDBのカラム構成より変えてください
        },
        ReturnValues: 'UPDATED_NEW'
    }
    var result
    try {
        result = await documentClient.update(params).promise()
    }
    catch (e) {
        result = e
    }

    return {
        statusCode: 200,
        body: result,
        id: event.requestId,
    }
}

// 更新
async function update(event) {
    const params = {
        TableName: tableName,
        Key: {
            id: event.item.id
        },
        UpdateExpression: 'set update_key=:update_key',
        ExpressionAttributeValues: {
          ':update_key': event.item.update_key, // ここはDynamoDBのカラム構成より変えてください
        },
        ReturnValues: 'UPDATED_NEW'
    }
    var result
    try {
        result = await documentClient.update(params).promise()
    }
    catch (e) {
        result = e
    }

    return {
        statusCode: 200,
        body: result,
    }
}

// 削除
async function destroy(event) {
    const params = {
        TableName: tableName,
        Key: {
          id: event.item.id
        }
    }

    var result
    try {
        result = await documentClient.delete(params).promise()
    }
    catch (e) {
        result = e
    }

    return {
        statusCode: 200,
        body: result,
    }
}
  • 作成も更新も、putを使わず、udpateを使い、

    ReturnValues: 'UPDATED_NEW'

    とすることで、作成、更新されたレコードがレスポンスとして返せるので、こうしました。 詳細は、

AWS Document WorkingWithItems

AWS Document GettingStarted NodeJS

に詳細ありますので、確認してみてください。


APIGatewayの設定

まずは、APIGatewayの設定をしてきます。AWSコンソールにログインし、APIGatewayに行きます。

APIの作成

  • 右上のAPIを作成から進みます apigateway1
  • 作成画面で、今回はREST APIを選択します apigateway2
  • API名を入れて、作成ボタンを押します apigateway3

リソースの作成

  • 作成したAPIでリソースを作成します apigateway4
  • 任意のリソース名を入力 apigateway4-2

メソッドの作成

  • 必要に応じてメソッドを作成する apigateway5
  • メソッドの「統合リクエスト」を選択
  • 先程作成したLamdbaが呼ばれるように設定する apigateway5-2
  • 先程作成したLambda関数名とそのリージョンを選択する apigateway5-2-2
  • lambdaに渡すパラメータをマッピングテンプレートで設定し定義する
  • ※例えば、なにかフォームからjsonを渡して、APIGatewayに渡ってきたパラメータをlambdaに渡し、処理をすることがあると思いますので、この設定で渡すようにします。
  • 画面下部、「マッピングテンプレート」を選択し、Content-Typeに
application/json
  • ※ここはjsonじゃなかったり、他の形でデータが渡ってくると想定される場合は、変えてください。 apigateway5-3
  • jsonデータをそのままlambdaにわたす場合、

    {
    "type": "$context.httpMethod",
    "item": $input.json('$')
    }

    ※ここではitemというキー名なので、lambdaでこのデータを参照するには、

    event.item

    typeは先程のメソッド名を渡す部分となります。

apigateway5-4

  • ※必要に応じて、書くメソッド等でマッピングテンプレートの設定をします

CROS の有効化

  • 有効にしないと違うドメインから叩けないので、有効にします apigateway6
  • ポチッと押すだけです apigateway6-2

デプロイ

  • APIをデプロイし実際たたけるようにします apigateway7
  • デプロイ先を設定しますが、なければ作成します apigateway7-2
  • エンドポイントはここで確認します apigateway7-3

次に、dynamoDBの設定をしていきます。



DynamoDBの設定

dynamoDBの設定をしていきます。DynamoDBってどんな感じなの?って人は、

▶ 参考 DynamoDBの料金を最適化しよう【初級編】

を見ると料金や仕組みがわかると思います。

キャパシティユニット

これは、ざっくりいうと、1キャパシティーユニットは1秒間に決められた回数の読み込み、書き込み容量を提供し、利用している時間分料金がかかります。 当然この値が大きければ大きいほど、処理能力は高いですが、その分料金がかさみますので、とりあえずは小さくして試すことをおすすめします。

オートスケーリング

オートスケーリング機能があるのですが、デフォルトではONになっています。この設定をOFFにしないと、キャパシティユニットの変更はできないようです。実際にプロダクションでリリースする際は、オートスケーリングしておいたほうがいいかもしれません(サービスによる)。 気になる人は詳しく調べてから使っていただいたほうがいいと思います。

DynamoDBってどんななの?

DynamoDBはselectしてくる際に、特定のkeyだけ決めてそのプライマリーキーからデータを取得したり、更新したりすることができ(indexの追加も可)、ご自身の環境に応じて、どんなkeyでデータを取得したり、変更したりするかで、key名やindexの追加が必要になりそうです。 プライマリーキーやindexに追加したkeyさえレコードにあれば、他のカラムは、レコードにって全然違くてもOKです。

例)idがプライマリーキーの場合

[
  {
    id: 1,
    name: "名前",
    tel: "電話番号"
  },
  {
    id: 2,
    nickname: "ニックネーム",
    address: {
      prefecture: "都道府県",
      city: "市町村区",
      code: "郵便番号"
    }
  }
]

それではテーブルの作成に進みます。

テーブルの作成

  • 作成から必要な内容を入力していきます。 apigateway8
  • テーブル名、プライマリーキー、デフォルト設定の使用のチェックを外す apigateway8-2
  • プライマリーキーの別に、必要なキーがあれば、index追加を行う apigateway8-3
  • オートスケーリングする場合はデフォルトでそれぞれ5となっており、オートスケーリングを使わない(チェックを外す)場合、キャパシティユニットの設定を手動で行うことができます。 apigateway8-4

これで、完了です。



まとめ

出来たAPI Gatewayのエンドポイントと表示されるURLを叩いてみてDynamoDBのデータの操作が出来ているか確認してみましょう。

本来サーバーを用意して、環境を構築して、DBの設定やサーバーサイド言語で、APIの実装をしないといけないところ、コンソールでカチカチ操作するだけで、簡単にAPIの実装ができました。

余談

僕は今回APIが誰でも叩かれては困るので、Congnitoでサインインしている場合しかたたけないようにAPI Gatewayに認証をかけた実装をしています。 この内容は後日書こうと思います。

🙏よかったらシェアお願いします🙏
うかい / 株式会社UKAI
うかい@エンジニア出身CEO株式会社UKAI
Nobuyuki Ukai

株式会社UKAI 代表取締役CEO。建築を専攻していたが、新卒でYahoo!Japanにエンジニアとして入社。その後、転職、脱サラ、フリーランスエンジニアを経て、田舎へ移住し、ゲストハウスの開業、法人成り。ゲストハウス2軒、焼肉店、カフェ、食品製造業を運営しています。詳細はこちらから

ホームページ作成、ECサイト掲載、商品ブランディング等、ご依頼やご相談は


CONACT
入力して下さい
WRITTEN BY
うかい / 株式会社UKAI
うかい@エンジニア出身CEO株式会社UKAI

Nobuyuki Ukai

株式会社UKAI 代表取締役CEO。建築を専攻していたが、新卒でYahoo!Japanにエンジニアとして入社。その後、転職、脱サラ、フリーランスエンジニアを経て、田舎へ移住し、ゲストハウスの開業、法人成り。ゲストハウス2軒、焼肉店、カフェ、食品製造業を運営しています。2020年コロナウイルスの影響で、ゲストハウスとカフェを閉店。現在は、ECサイト新規リリース、運営と、黒毛和牛の牝牛ブランディングをしメディア立ち上げ。牝牛のお肉と、独自食品開発した食品をまもなく販売開始予定。詳細はこちらから

COPYRIGHT © 2021 UKAI CO., LTD. ALL RIGHTS RESERVED.