Lambda連携で学ぶAmazon API Gateway解説!
2015/12/17
こんにちは、@s_tsukaです。
今回はAmazon API Gateway各種機能、使い方、Lambda連携もろもろを学んで行きます。
Amazon API Gatewayとは
AWSのサービスの一つでその名の通りAPIのGatewayです。通常であれば何らかのFWや言語を使ってAPIを開発すると思います。例えばNode、Spring3、Play2、Spray、Go、Java、Scala、etc。これらにキャッシュやルーティングを変えるためにApacheやNginxの皮を被せることがあるかと思いますが、この皮の超強力なものがAPI Gatewayだと思ってもらえれば良いです。
機能の概要については公式のTOPページが参考になると思います。
https://aws.amazon.com/jp/api-gateway/
Amazon Lambdaとは
AWSのサービスの一つで、コードを実行する基盤です。コードを実行するならば別に物理ホストでもどこかのVPCでもAmazon EC2でも良い訳ですが、それらを本番環境で運用するとなると場合によってはスケーリングやメンテナンスを考慮する必要があります。Lambdaを使えば色々とマネージされているので、それらの考慮の度合いが低くなるあるいは無くなるという恩恵を受けられます。
詳しくはこちらの動画(3分)などが参考になるかと思います。
- コードの実行基盤を用意したいけど、EC2のようなホストを持ちたく無い(管理したく無い)
- リクエスト数が朝と夜でかなり異なるので、自動でスケールして欲しい
- サーバの利用料を減らしたい
と言ったケースに使えるかと思います。
API GatewayとLambdaの連携
API Gatewayでendpointを用意し、そのバックエンドをLambda Functionで繋ぐことでHTTPを使ってLambdaを動かすことができるようになります。
これは簡単に実現できます。詳細は次の節にて。
blueprint(設計図・テンプレ)からLambda、API連携を速攻作成
API GatewayとLambdaの連携は、実は公式がサンプルを用意してくれています。
そしてそれについて解説しているサイトも既に存在します。
【新機能】Amazon API Gatewayを使ってAWS LambdaをSDKなしでHTTPS越しに操作する
この通りやれば良いのですが、1点だけ注意点があって、API Gatewayは東京リージョンではまだ使えませんので、東京リージョンで作業するとAPI Gateway作成あたりでエラーが出ます。なのでバージニア北部などを選んでおきましょう。おそらくですが、Lambdaを東京で作った場合はLambdaの画面からではなく、API Gatewayの画面から連携設定すると上手く行くかと思います。
(現在は東京リージョンでもAPI Gatewayを利用できるようになりました。http://aws.typepad.com/aws_japan/2015/10/api-gatewaynow-available-in-asia-pacific.html)
ここではサンプル作成は上記の記事に任せるとして、そこからさらに発展して行きます。
まずは上記の記事通りサンプルを作成してみてください。
サンプルのオペレーションを一通り実行してみる
上記の記事ではechoしか実行していませんでした。実際にはcreateやlistなど他のオペレーションも用意されています。
console.log('Loading function'); var doc = require('dynamodb-doc'); var dynamo = new doc.DynamoDB(); exports.handler = function(event, context) { //console.log('Received event:', JSON.stringify(event, null, 2)); var operation = event.operation; delete event.operation; switch (operation) { case 'create': dynamo.putItem(event, context.done); break; case 'read': dynamo.getItem(event, context.done); break; case 'update': dynamo.updateItem(event, context.done); break; case 'delete': dynamo.deleteItem(event, context.done); break; case 'list': dynamo.scan(event, context.done); break; case 'echo': context.succeed(event); break; case 'ping': context.succeed('pong'); break; default: context.fail(new Error('Unrecognized operation "' + operation + '"')); } };
それらも利用してみます。
create
DynamoDBの特定のテーブルにアイテムを登録する操作です。DynamoDBで予めテーブル(sample_table, hashキーは”id”)を作成しておき、以下のbodyでPOSTします。
{ "operation": "create", "TableName": "sample_table", "Item": { "id": "1", "name": "API_TEST" } }
DynamoDBに登録されました。
read
readは取得ですが、対象のキーが必要です。ですので、このようにします。
{ "operation": "read", "TableName": "sample_table", "Key": { "id": "1" } }
結果はこのようなjsonが返ります。
{ "Item": { "name": "API_TEST", "id": "1" } }
update
UPDATEは更新です。
このようなjsonをbodyにしてAPI callすると
{ "operation": "update", "TableName": "sample_table", "Key": { "id": "1" }, "UpdateExpression": "set #name = :val1", "ExpressionAttributeValues": { ":val1": "UPDATED_API_TEST" }, "ExpressionAttributeNames": { "#name" : "name" } }
結果は{}というjsonが返るのですが、ちゃんと更新されています。
テーブルのフィールド名に「name」というDynamoDB予約語を使っているのでExpressionAttributeNamesが必要になって少し複雑ですが・・・。
基本的にbodyに入れて投げるjsonはDynamoDBの仕様と同じですね。
delete
投げるbodyはこれです。
{ "operation": "delete", "TableName": "sample_table", "Key": { "id": "1" } }
レスポンスは{}が返りますが、正しくきえています。
list
単純ですね。
{ "operation": "list", "TableName": "sample_table" }
予めcreateで何件か登録しておいた後で叩くとこういうjsonが返ります。
{ "Count": 3, "Items": [ { "name": "API_TEST2", "id": "2" }, { "name": "API_TEST", "id": "1" }, { "name": "API_TEST3", "id": "3" } ], "ScannedCount": 3 }
echoとping
echoは既にクラスメソッドのblogでサンプルが出ていたので省略します。
pingも”pong”という文字列が返ってくるだけなので省略します。
よりRESTfulにする(発展)
ここからいよいよAPI Gatewayを把握するために色々とカスタマイズして行きます。
ここまでは単純に作成したサンプルを実行してきましたが、全ての操作はPOSTリクエストで行い、bodyのoperationフィールドによって処理を切り替えていました。しかしRESTに則るならばおそらくこれはNGで、POST, GET, PUT, DELETE (CRUD)によって切り替えるべきです。
早速この切り替えを行って行きます。ここでは簡略化のためにPOSTとGET(1件と一覧)のみサポートします。
設計
まずは対象とするリソース、entityですが以下とします。予めpersonsテーブルもDynamoDBに作成しておきます。
Person( id: Int, name: String )
URIはこんな感じです。
POST /api/persons #1件登録 GET /api/persons #一覧取得 GET /api/persons/{id} #1件取得
開発
コードを用意します。操作は上記の通り3つなので、Lambda Functionは3つ用意します。色々と微妙な点はありますが、そこは一旦無視してください。
POST用
console.log('Loading function'); var doc = require('dynamodb-doc'); var dynamo = new doc.DynamoDB(); exports.handler = function(event, context) { var dynamoRequest = { "TableName": "persons", "Item": { "id": event.id, "name": event.name } }; dynamo.putItem(dynamoRequest, context.done); };
(1件)GET用
console.log('Loading function'); var doc = require('dynamodb-doc'); var dynamo = new doc.DynamoDB(); exports.handler = function(event, context) { console.log('Received event:', JSON.stringify(event, null, 2)); var dynamoRequest = { "TableName": "persons", "Key": { "id": event.id.toString() } }; dynamo.getItem(dynamoRequest, context.done); };
(一覧)GET用
console.log('Loading function'); var doc = require('dynamodb-doc'); var dynamo = new doc.DynamoDB(); exports.handler = function(event, context) { var dynamoRequest = { "TableName": "persons" }; dynamo.scan(dynamoRequest, context.done); };
LambdaとAPI Gatewayの設定
上記のコードをLambdaに登録します。
API Gatewayの登録は少しだけ工夫がいるのでLambdaの画面ではなくAPI Gatewayの画面から行います。
まずはこのまっさらな画面を開きます。
CreateResourceボタンでpersonsリソースを作成します。
personsリソースを選択した状態で再度CreateResourceボタンを押してpersonIdリソースを作成します。ここで、Pathの部分は{id}とします。{}で囲むとそれがURL Parameterだと認識されます。
リソースの作成が完了したら次はメソッドの登録を行います。/personsの下にはPOSTとGETを、/persons/{id}の下にはGETを作成します。
最後にGET /persons/{id}だけ追加設定を行います。{id}をLambda function側に正しく渡してあげるために設定を行います。
Integration Requestを選んで以下の画像のようにMappingの設定を行います。
このあたりのQuery parameter、URL parameterのマッピングについては公式ドキュメントのこのあたりが参考になると思います。
デプロイ
設定内容が即時反映される訳ではなくて、API Gatewayの設定はステージという単位で区切られています。なので、Deployを行って新たなステージを作成します。
Deployボタンを押してprodステージにデプロイを行います。
ここまでの作業で準備は整いました・・・!いよいよ使ってみましょう!
テスト
折角なのでAPI GatewayのTest機能ではなく、実際のURIを使って試してみましょう。
ステージ画面で払い出されたURIを確認できます。
実際にこのURIを使ってPOST2回とGET(一覧)、GET(1件)を実行してみます。
# POST https://ukdekt9vr9.execute-api.us-east-1.amazonaws.com/prod/persons # body { "id": "1", "name": "tsukaby" } # result {} # POST https://ukdekt9vr9.execute-api.us-east-1.amazonaws.com/prod/persons # body { "id": "2", "name": "tsukaby2" } # result {} # GET https://ukdekt9vr9.execute-api.us-east-1.amazonaws.com/prod/persons # result { "Count": 2, "Items": [ { "name": "tsukaby2", "id": "2" }, { "name": "tsukaby", "id": "1" } ], "ScannedCount": 2 } # GET https://ukdekt9vr9.execute-api.us-east-1.amazonaws.com/prod/persons/1 # result { "Item": { "name": "tsukaby", "id": "1" } }
ちゃんと動きました!
まとめ
Lambda functionとAPI Gatewayを連携させる方法を解説しました。また、サンプルを少し改良してRESTfulなAPIを作る方法について解説しました。
API Gatewayの他の機能については別の記事で解説して行きたいと思います。
みなさんもAPI Gateway一度試してみてはいかがでしょうか?