DynamoDBの設計は発想も大事!? 異なるパラメータでも同じ応答をするWebAPIを作ってみた(API Gateway、Lambda、DynamoDB)

はじめに

サーバーレス開発部の藤井元貴です。

DynamoDBのレビュー会があり、そこで面白い使い方を知ったのでご紹介します。

実際に使う場合は、DynamoDBの設計をしっかりと実施してください。

ちなみに、DynamoDBのベストプラクティスはこちらです。

AWS公式が公開している資料も参考になると思います。

おすすめの方

  • DynamoDBに興味がある
  • AWS SAM(CloudFormation)でDynamoDBを使いたい
  • AWS SAMでPathパラメータを使うAPIを作りたい

DynamoDBの面白い使い方

DynamoDBのユニークなKeyに対して、エイリアスレコード(別名用)を用意します。(エイリアスレコードは公式名称ではなく、勝手につけた名称です)

これにより、「Keyの表記ゆらぎ」があっても、DynamoDB内のレコードを特定できます!!

たとえば?

次のようなWebAPIがあるとします。

Path Method
/color/{param} GET

この{param}に対して、次のように異なるモノ(色名と色コード)を与えても、同じJSONを返却するように実装したいです。

  • color/red
  • color/ff0000
{
    "name":"red",
    "name_ja":"赤",
    "r":255,
    "g":0,
    "b":0
}

redff0000をそれぞれKeyとして、DynamoDBに同じ情報を持つのは無駄です。

name (Hash Key) name_ja r g b
red 255 0 0
ff0000 255 0 0

そこで、DynamoDBのテーブルを次のように設計し、エイリアスレコード(別名用)を設けます。

name (Hash Key) reference name_ja r g b
red 255 0 0
ff0000 red

これにより、次の動作を行えば、「red」と「ff0000」で同じ内容を返却できます。

  • /color/redの場合は、redを取得して返却する
  • /color/ff0000の場合は、ff0000を取得し、referenceを参照し、redを取得して返却する

目から鱗が落ちました。

エイリアスレコード以外の全データ取得は?

reference以外の項目にGSI(グローバル・セカンダリ・インデックス)を設定すると取得できます。

今回はname_jaにGSIを設定しています。

やってみた

環境

項目 バージョン
macOS High Sierra 10.13.6
AWS CLI aws-cli/1.16.89 Python/3.6.1 Darwin/17.7.0 botocore/1.12.79
AWS SAM CLI 0.10.0
Python 3.6

サーバーレスなアプリの作成

AWS SAMを使用して作成します。

プロジェクトフォルダの作成

sam init --runtime python3.6 --name TryDynamoDB

Lambda関数とtemplateファイル

app.pyとtemplate.yamlは下記です。

見にくいですが、下記を一度に返却しています。

  • pathで指定した1つのレコード
  • 全てのレコード(エイリアスレコードを除く)

S3バケットの作成

コード等を格納するためのS3バケットを作成します。作成済みの場合は飛ばします。

aws s3 mb s3://cm-fujii.genki-sam-test-bucket

build

下記でビルドします。

sam build

package

コード一式をS3バケットにアップロードします。

sam package \
    --output-template-file packaged.yaml \
    --s3-bucket cm-fujii.genki-sam-test-bucket

deploy

デプロイします。

sam deploy \
    --template-file packaged.yaml \
    --stack-name TryDynamoDB \
    --capabilities CAPABILITY_IAM

動作確認

DynamoDBのテーブルにItemを追加

まずはparam=nameのItemを追加します。

aws dynamodb put-item \
    --table-name TryDynamoDB-ColorTable \
    --item '{"name":{"S":"red"}, "name_ja":{"S":"赤"}, "r":{"N":"255"}, "g":{"N":"0"}, "b":{"N":"0"}}'
aws dynamodb put-item \
    --table-name TryDynamoDB-ColorTable \
    --item '{"name":{"S":"green"}, "name_ja":{"S":"緑"}, "r":{"N":"0"}, "g":{"N":"255"}, "b":{"N":"0"}}'
aws dynamodb put-item \
    --table-name TryDynamoDB-ColorTable \
    --item '{"name":{"S":"blue"}, "name_ja":{"S":"青"}, "r":{"N":"0"}, "g":{"N":"0"}, "b":{"N":"255"}}'

続いて、param=rgbのItemを追加します。

aws dynamodb put-item \
    --table-name TryDynamoDB-ColorTable \
    --item '{"name":{"S":"ff0000"}, "reference":{"S":"red"}}'
aws dynamodb put-item \
    --table-name TryDynamoDB-ColorTable \
    --item '{"name":{"S":"00ff00"}, "reference":{"S":"green"}}'
aws dynamodb put-item \
    --table-name TryDynamoDB-ColorTable \
    --item '{"name":{"S":"0000ff"}, "reference":{"S":"blue"}}'

こうなりました。

WebAPIのエンドポイントを確認

Web画面ポチポチでも良いですが、せっかくなのでコマンドを使います。

$ aws cloudformation describe-stacks --stack-name TryDynamoDB --query 'Stacks[].Outputs'
[
    [
        {
            "OutputKey": "HelloWorldApi",
            "OutputValue": "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/color/{param}",
            "Description": "API Gateway endpoint URL for Prod stage for Hello World function"
        }
    ]
]

OutputValueがWebAPIのエンドポイントです!

いざ、確認!

まずは、param=nameを確認します。

curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/color/red

結果はこちら。redが取得できました。(\u8d64は「赤」です) ついでに、「実体レコードのみ(3個)」も取得できました!

{
  "get_item":{
    "r":255,
    "b":0,
    "name":"red",
    "name_ja":"\u8d64",
    "g":0
  },
  "scan":[
    {
      "r":255,
      "b":0,
      "name":"red",
      "g":0,
      "name_ja":"\u8d64"
    },
    {
      "r":0,
      "b":0,
      "name":"green",
      "g":255,
      "name_ja":"\u7dd1"
    },
    {
      "r":0,
      "b":255,
      "name":"blue",
      "g":0,
      "name_ja":"\u9752"
    }
  ]
}

続いて、param=rgbを確認します。

curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/color/ff0000

結果はこちら。redが取得できました。(\u8d64は「赤」です)

同じ内容ですね!!

{
  "get_item":{
    "r":255,
    "b":0,
    "name":"red",
    "name_ja":"\u8d64",
    "g":0
  },
  "scan":[
    {
      "r":255,
      "b":0,
      "name":"red",
      "g":0,
      "name_ja":"\u8d64"
    },
    {
      "r":0,
      "b":0,
      "name":"green",
      "g":255,
      "name_ja":"\u7dd1"
    },
    {
      "r":0,
      "b":255,
      "name":"blue",
      "g":0,
      "name_ja":"\u9752"
    }
  ]
}

さいごに

DynamoDBは設計(使い方)次第でいろんな事ができると実感しました。設計力を磨いていきたいですね!

参考