Edited at

初めての、LambdaとDynamoDBを使ったAPI開発


はじめに

LambdaとDynamoDB使ってAPI作ることになったんで、いろいろ試したメモ

詰まった点や、解決法など、参考になれば。(主に自分用のメモですが)

なおメソッドはPOSTでやっています。GETの記事が多かったので参考になれば。


この記事でやること


  • DynamoDBでテーブル作成

  • Lambda上でDynamoDBの値を取得(select)

  • Lambda上でDynamoDBに値を挿入(insert)

  • Lambda上でテスト

  • API Gatewayの設定

  • curlによるリクエストとレスポンス


DynamoDBでテーブル作成

テーブルを作ります。

スクリーンショット 2018-01-18 10.22.28.png

「テーブルの作成」をクリックし

スクリーンショット 2018-01-18 10.22.52.png

テーブル名とプライマリキー、必要であればソートキーを入力します。

DynamoDBはテーブル作成時にはPrimary KeyとSort Keyのみの指定であとはレコード挿入時に設定します。


レコードをコンソール上から挿入する

レコードを挿入します。

スクリーンショット 2018-01-18 10.27.12.png

テーブルを選択し、「項目」タブ内にある「項目の作成」をクリックします。

スクリーンショット 2018-01-18 10.27.39.png

この画面で画面左の+ボタンを押すとカラムを追加できるので、ここでカラムを追加します。

なおカラムは永続的なものはプライマリキーとソートキーのみなので逐一設定する必要があります。


Lambdaで関数を作成する


IAMでDynamoDBにアクセス可能なロールを作成

Lambdaで関数を作成する際にロールを求められるので先に作成します。

スクリーンショット 2018-01-18 10.36.41.png

「ロールの作成」をクリック

スクリーンショット 2018-01-18 10.38.48.png

Lambdaを選択

スクリーンショット 2018-01-18 10.38.34.png

検索窓にdynamoなんちゃらと入れると絞り込まれるので、

AmazonDynamoDBFullAccess

AWSLambdaDynamoDBExecutionRole

をチェックし次へ

スクリーンショット 2018-01-18 10.39.11.png

確認画面でロール名を入力し作成完了


関数の作成

スクリーンショット 2018-01-18 10.34.21.png

「関数の作成」をクリック

どうでもいいですが、myZaifChartは以前Alexaやった時に作ったものです。

スクリーンショット 2018-01-18 10.48.11.png

「一から作成」を選択し

ロールは先ほど作成したものがプルダウンに表示されるのでそれを選択します。

名前は好きなものを。

入力したら「関数を作成」ボタンを押します。


Lambda上でDynamoDBの値を取得


scan

全取得するにはscan()を使います。


index.js

var AWS = require('aws-sdk');

var dynamo = new AWS.DynamoDB.DocumentClient({
region: 'us-east-1'
});

exports.handler = (event, context, callback) => {
var params = {
TableName: "hoge_user"
};
console.log("event:", event);
dynamo.scan(params, function(err, data) {
console.log("dynamo_data:", data);
console.log("dynamo_err:", err);
context.done(null, data);
});
};


但し、scanは常にテーブル全体に読み取りが走ってしまうため、レコード数が増加し続けるテーブルに対しscanを行っていると毎月の費用がぐんぐん上昇していく羽目に遭います。

後述するgetやqueryをなるべく使うようにする方が賢明です。


get

1件だけ取得したい場合はgetを使います。


index.js

var AWS = require('aws-sdk');

var dynamo = new AWS.DynamoDB.DocumentClient({
region: 'us-east-1'
});

exports.handler = (event, context, callback) => {
var params = {
TableName: "hoge_user",
Key:{
user_id: "20180117_01abcde",
created_at: 1516170458
}
};
console.log("event:", event);
dynamo.get(params, function(err, data) {
console.log("dynamo_data:", data);
console.log("dynamo_err:", err);
context.done(null, data);
});
};


getは1件だけ取得するメソッドのため、ソートキーまで設定している場合にはプライマリキーとソートキーの両方を引数に設定しないと取得できません。

プライマリキーのみの設定の場合には引数は1つで大丈夫です。


query

queryは1〜n件を取得するために使います。

getと似ていますがソートキーまで設定した場合にプライマリキーは重複して入力できますので、そういった場合には複数のレコードを取得することができます。


index.js

var AWS = require('aws-sdk');

var dynamo = new AWS.DynamoDB.DocumentClient({
region: 'us-east-1'
});

exports.handler = (event, context, callback) => {
var params = {
TableName: "hoge_user",
KeyConditionExpression: "#key = :str",
ExpressionAttributeNames:{
"#key": "user_id"
},
ExpressionAttributeValues: {
":str": "20180117_01abcde"
}
};

console.log("event:", event);
dynamo.query(params, function(err, data) {
console.log("dynamo_data:", data);
console.log("dynamo_err:", err);
context.done(null, data);
});
};



Lambda上でDynamoDBに値を挿入

こんな感じで


index.js

var AWS = require('aws-sdk');

var dynamo = new AWS.DynamoDB({
region: 'us-east-1'
});

exports.handler = (event, context, callback) => {

var date = new Date();
var time1 = date.getTime();
var unixtime_sec = Math.floor(time1 / 1000);

console.log("event:", event);
dynamo.put({
"TableName": "hoge_user",
"Item": {
"user_id": event.user_id,
"device_token": event.device_token,
"created_at": unixtime_sec,
"updated_at": unixtime_sec
}
}, function( err, data ) {
console.log("dynamo_err:", err);
context.done(null, data);
});
};



Lambda上でテスト

こんな感じでテスト設定し動作を確認します。

スクリーンショット 2018-01-18 13.11.09.png

ここまで出来れば、Lambdaを使ってDynamoDBと連携するところまでは上手くいっています。

ここからAPI Gatewayの部分を設定していきます。


API Gatewayの設定

API Gatewayの設定はLambdaのコンソールだと見れる項目が少なすぎてさっぱりなので、

API Gatewayのコンソールから設定する方が良いです。

LambdaコンソールでAPI追加するとメソッドもANYになってしまい、これも混乱のもとです。


APIの作成

スクリーンショット 2018-01-18 13.15.18.png

「APIの作成」をクリック

スクリーンショット 2018-01-18 13.16.05.png

「新しいAPI」を選択し、API名は好きなものを。エンドポイントタイプはデフォルトで選択されているエッジ最適化を使います。

スクリーンショット 2018-01-18 13.16.38.png

作るとこんな状態になります

「アクション」→「メソッドの作成」をクリックします。

中央のペインにプルダウンが表示されますので、ここではPOSTを選択します。

スクリーンショット 2018-01-18 13.17.03.png

統合タイプ:Lambda関数

Lambdaプロキシ統合の使用:オフ

Lambdaリージョン:Lambda関数を作ったリージョンを選択

→リージョン選択するとメソッドを指定するフォームが表示されるので、Lambdaで作った関数名を入力

スクリーンショット 2018-01-18 13.17.39.png

終わるとこんな画面になります。

「アクション」プルダウンから「APIのデプロイ」をクリック

ステージ名を聞かれるので新規に作成します。名称は好きなものを。

スクリーンショット 2018-01-18 13.18.48.png

終わるとこんな画面になります。


curlでテスト

ここで一旦、curlでリクエストしてみましょう。


console

$ curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' https://*********************.amazonaws.com/dev -d '{"user_id":"20180118_01abcde","device_token":"token20180118_01"}' --dump-header -

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 4
Connection: keep-alive
Date: Thu, 18 Jan 2018 04:21:12 GMT
x-amzn-RequestId: **************************
X-Amzn-Trace-Id: **************************
X-Cache: Miss from cloudfront
Via: 1.1 **************************.cloudfront.net (CloudFront)
X-Amz-Cf-Id: **************************

おっしゃあああ!できた!


APIキーの設定

このままだとザルなのでAPIキーを設定します。

APIキーは作成するだけだと効果がありません。

APIキー、使用量プラン、APIへの設定の3点セットで紐付けを行います。

スクリーンショット 2018-01-18 14.01.22.png

左ペインでAPIキーを選択しすると上の画面になります。

「アクション」→「APIキーの作成」へ

スクリーンショット 2018-01-18 14.01.42.png

名前は好きなものを

APIキーが作成されました。


使用量プランの設定

使用量プランと書いてありますが課金設定ではありません(課金を匂わす文言だとは思いますが)

左ペインの「使用料プラン」を選択し、表示された画面から「作成」ボタンをクリックします。

スクリーンショット 2018-01-18 14.02.48.png

名前:好きなものを

スロットリングの有効化:オフ

クォータ:オフ


APIステージの関連付け

スクリーンショット 2018-01-18 14.03.22.png

ここでAPIとの紐付けを行います

「APIステージの追加」をクリックするとテーブルにプルダウンが表示されるので、

紐付けたいAPIとステージ(デプロイ時に作ったやつ)を選択します


使用料プランのAPIキー

スクリーンショット 2018-01-18 14.04.29.png

ここで先ほど作ったAPIキーを設定します。

スクリーンショット 2018-01-18 14.05.11.png

メソッド側でAPIキーの必要性をTrueにします。

ここまで終わったらデプロイしましょう。デプロイしていないせいで思った通り動かないということがよくあります。


curlでテスト

リクエストしてみましょう


console

$ curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' https://****************************.amazonaws.com/dev -d '{"user_id":"20180118_02abcde","device_token":"token20180118_02"}' --dump-header -

HTTP/1.1 403 Forbidden
Content-Type: application/json
Content-Length: 24
Connection: keep-alive
Date: Thu, 18 Jan 2018 05:21:49 GMT
x-amzn-RequestId: ****************************
x-amzn-ErrorType: ForbiddenException
X-Cache: Error from cloudfront
Via: 1.1 ****************************.cloudfront.net (CloudFront)
X-Amz-Cf-Id: ****************************

{"message":"Forbidden"}


はい、先ほどは200だったものが403になりました。

次にAPIキーを設定しリクエストします。


console

$ curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'x-api-key: ****************************' https://****************************.amazonaws.com/dev -d '{"user_id":"20180118_02abcde","device_token":"token20180118_02"}' --dump-header -

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 4
Connection: keep-alive
Date: Thu, 18 Jan 2018 05:23:02 GMT
x-amzn-RequestId: ****************************
X-Amzn-Trace-Id: ****************************
X-Cache: Miss from cloudfront
Via: 1.1 ****************************.cloudfront.net (CloudFront)
X-Amz-Cf-Id: ****************************

おっしゃあああ!できた!


ハマった問題


うまく引数が渡せない問題

ここでうまく引数を渡せず変だなーとなっている場合、メソッドがANYになっていたらPOSTやGETに変更するとうまくいきます。

私はそれで解決しました。


設定が反映されない問題

APIキー不要の設定にしたにも関わらずForbiddenが出続けるなど

デプロイ忘れてないか確認しましょう


それでも解決しない時

APIを作り直しましょう!w


追記、APIに独自ドメインを設定する方法


ドメイン設定、SSL証明書発行

こちらを参考に証明書の発行まで進めてください。

https://qiita.com/miutex/items/1f515bc21111a5cf3fdb


API Gatewayで設定

「カスタムドメイン名の作成」でエンドポイント設定をし、先程取得したACM証明書をセットします。

あとはベースパスマッピングでデプロイしたものを選択すればOKです。


参考

https://qiita.com/kojiro_ueda/items/303f2466e11b55e5ec21

https://qiita.com/tentatsu/items/c45bcc4062f1a6d4cf2a

https://qiita.com/Yuki_BB3/items/83198b4d9daca7ccd746

https://www.lanches.co.jp/blog/6776