Amazon API Gateway + LambdaのWeb APIをサクっと作るfluctを試してみた
ども、大瀧です。
API GatewayとLambdaの組み合わせはサーバーレスでWebAPIをホストできる新しいフレームワークとして、大きな注目を集めていますね。ただ、1つのAPIを定義するためにはManagement Consoleの画面で各サービスをそれぞれ構成する必要があり、かなり手間がかかる印象です。そこで、それらの作業を簡単に行うツールとして@r7kamuraさんが開発するfluctを使ってみたいと思います。
fluctとは
GitHubのDescriptionにWeb application framework for Amazon Lambda and Amazon API Gateway.とある通り、RailsなどのWebアプリケーションフレームワーク風にAPI Gateway+Lambdaを扱うツールです。
検証環境
- OS : MaxOS X Yosemite
- Node.js : v0.12.4
- npm : バージョン 2.10.1
- fluct : バージョン 0.2.4
インストール
fluctはNode.jsで記述されており、npm
でインストールします。fluct
コマンドのPATHを通すために-g
フラグを付けます。
$ npm install -g fluct /usr/local/bin/fluct -> /usr/local/lib/node_modules/fluct/bin/fluct fluct@0.2.4 /usr/local/lib/node_modules/fluct ├── commander@2.8.1 (graceful-readlink@1.0.1) ├── node-aws-lambda@0.1.4 (async@1.4.2) ├── ejs@2.3.3 ├── yazl@2.2.2 (buffer-crc32@0.2.5) ├── mkdirp@0.5.1 (minimist@0.0.8) ├── glob@5.0.14 (path-is-absolute@1.0.0, inherits@2.0.1, once@1.3.2, inflight@1.0.4, minimatch@2.0.10) ├── moment@2.10.6 ├── aws-sdk@2.1.44 (xmlbuilder@0.4.2, xml2js@0.2.8, sax@0.5.3) ├── amazon-api-gateway-client@0.1.3 (stackable-fetcher-aws-signer-v4@0.0.1, stackable-fetcher@0.3.0) └── express@4.13.3 (escape-html@1.0.2, merge-descriptors@1.0.0, cookie@0.1.3, array-flatten@1.1.1, cookie-signature@1.0.6, utils-merge@1.0.0, fresh@0.3.0, range-parser@1.0.2, methods@1.1.1, vary@1.0.1, path-to-regexp@0.1.7, content-type@1.0.1, etag@1.7.0, parseurl@1.3.0, serve-static@1.10.0, content-disposition@0.5.0, depd@1.0.1, qs@4.0.0, on-finished@2.3.0, debug@2.2.0, finalhandler@0.4.0, accepts@1.2.12, type-is@1.6.6, send@0.13.0, proxy-addr@1.0.8) $
これで準備OKです。
スケルトンファイルの生成
fluctは、Railsなどと同様に特定のディレクトリ配下にアプリケーションファイルを配置します。アプリケーションのスケルトンはfluct new <API名>
で作成します。
$ fluct new apisample Created ./apisample Created ./apisample/.gitignore Created ./apisample/actions Created ./apisample/actions/.keep Created ./apisample/package.json $
APIの構成は<API名>/package.json
ファイル、APIのアクション(API GatewayのResource/Method、Lambda Function定義のセット)は<API名>/actions/
ディレクトリ以下に追加していきます。アクションのスケルトンはfluct generate <アクション名>
で作成します。
$ cd apisample/ $ fluct generate list_users Created ./actions/list_users Created ./actions/list_users/index.js Created ./actions/list_users/package.json
<API名>/actions/<アクション名>/
ディレクトリが作成され、アクションの構成を<アクション名>/package.json
ファイル、Lambda Functionの定義を<アクション名>/index.js
として生成されます。スケルトンには、以下のようにダミーのAPI Gateway MethodとしてGET
、Resourceとして/dummy
がセットされます。
{ "name": "list_users", "version": "0.0.1", "private": true, "fluct": { "contentType": "text/html", "httpMethod": "GET", "path": "/dummy" } }
Lambda Functionのスケルトンは以下のようになっています。レスポンスとしてHello, world!
を返す最低限の記述ですね。
exports.handler = function (event, context) { context.succeed('Hello, world!'); };
あとは、好みのResource/Methodに変更し、Lambda Functionにアプリケーションロジックを追加していく感じです。Lambda FunctionでNodeモジュールを利用する場合はアクションのpackage.json
ファイルにdependency
を追加しましょう *1。今回はお試しなので、そのままにしておきます。定義したアクションの一覧は、これまたRailsのようにfluct routes
で一覧表示できます。
$ cd ../../ $ fluct routes GET /dummy #list_users $
良い感じですね。
ローカルモード
fluctにはローカルモード(fluct server
)が備わっており、デプロイ前の動作確認ができます。
$ fluct server Server starting on http://127.0.0.1:3000
別の端末やWebブラウザからhttp://127.0.0.1:3000/<アクション名>
にアクセスしてみると...
$ curl http://127.0.0.1:3000/dummy/ Hello, world! $
Lambda Functionに記述した処理のレスポンスが返ってきますね!fluctの依存モジュールの様子から、内部でExpressが実行され、fluctのディレクトリレイアウトに合わせてルーティングされているようです。Lambda FunctionではAWS SDK for JavascriptでAWSのAPIにアクセスするケースが多いと思います。ローカルモードではローカルマシンでAWS SDKを実行するのと同様の設定でAPIキーを読み込みますので、~/.aws/credentials
ファイルなどを事前に確認しておきましょう。
AWSへのデプロイ
AWSにデプロイするためには、Lambdaの実行ロールをAPIの構成ファイル(<API名>/package.json
)にセットしておく必要があります。Lambda Functionで行う処理に合わせて適当なIAMポリシーを割り当てたロールのARNを確認しておきましょう。
{ "name": "apisample", "version": "0.0.1", "private": true, "fluct": { "restapiId": null, "roleArn": "arn:aws:iam::XXXXXXXXXXXX:role/lambda_exec_role" } }
では、アクション定義を元にAPI GatewayとLambdaの設定をデプロイするfluct deploy
を実行します。以下の処理が一度に実行され、結構壮観です!
- Lambda Functionのアップロード
- Lambda Functionメタデータ(Execute Roleの設定)
- API Gateway APIの作成
- API Gateway Resourceの作成
- API Gateway Methodの作成、Lambda Event Sourceの紐付け
- API Gateway Stageのデプロイ
なお、現時点ではLambda Functionのアップロードとメタデータ更新の処理順序の関係で、初回実行はエラーになるようです。再度実行すると正常に完了します。 → バージョン0.2.5で修正されました。
$ fluct deploy Created zip: ./actions/action/lambda.zip ResourceNotFoundException: Function not found: arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:action at Object.extractError (/usr/local/lib/node_modules/fluct/node_modules/aws-sdk/lib/protocol/json.js:43:27) at Request.extractError (/usr/local/lib/node_modules/fluct/node_modules/aws-sdk/lib/protocol/rest_json.js:37:8) at Request.callListeners (/usr/local/lib/node_modules/fluct/node_modules/aws-sdk/lib/sequential_executor.js:105:20) at Request.emit (/usr/local/lib/node_modules/fluct/node_modules/aws-sdk/lib/sequential_executor.js:77:10) at Request.emit (/usr/local/lib/node_modules/fluct/node_modules/aws-sdk/lib/request.js:595:14) at Request.transition (/usr/local/lib/node_modules/fluct/node_modules/aws-sdk/lib/request.js:21:10) at AcceptorStateMachine.runTo (/usr/local/lib/node_modules/fluct/node_modules/aws-sdk/lib/state_machine.js:14:12) at /usr/local/lib/node_modules/fluct/node_modules/aws-sdk/lib/state_machine.js:26:10 at Request.<anonymous> (/usr/local/lib/node_modules/fluct/node_modules/aws-sdk/lib/request.js:37:9) at Request.<anonymous> (/usr/local/lib/node_modules/fluct/node_modules/aws-sdk/lib/request.js:597:12) Uploaded function: action $ fluct deploy Created zip: ./actions/action/lambda.zip Uploaded function: action Created restapi: 01234567 Updated endpoint: GET /dummy Deployed: https://01234567.execute-api.us-east-1.amazonaws.com/production $
では、最後に表示されたAPI GatewayのエンドポイントにResourceの/dummy
を付与してアクセスしてみます。
$ curl https://0usu7lppaj.execute-api.us-east-1.amazonaws.com/production/dummy Hello, world! $
API GatewayからLambdaがキックされ、レスポンスが返ってきました!
まとめ
LambdaとAPI GatewayをWebアプリケーションフレームワークのように扱えるツール、fluctをご紹介しました。fluctを使えばさくさくAPIを追加してアプリ開発が捗りそうですね!
fluctは早いペースで開発が進んでいるため、コマンドオプションやファイル構成が度々変更されます。試す際には最新のUsage(GitHubのREADME)をご確認ください!
参考URL
脚注
- npm installの実行はこの後のデプロイ時(fluct deploy)に自動実行されますが、ローカルモード(fluct server)では実行されないため、必要に応じて各アクションのディレクトリでnpm installを実行しましょう。 ↩