はじめに
最近はどちらかというとGraphQLやRelayで遊んでいる時間が長かったので、Falcorネタは久しぶりです。
GraphQL、Falcorの双方とも、Clinet - Server間の通信を効率化するための技術です。したがって、僕のようなフロントの人間が「ちょっとGraphQL/Falcor触ってみたい」と思うと、いちいちGraphQL/FalcorのEndpointとなるServerを用意する必要が出てくる訳です。
GraphQLの場合, ReindexやGraph.coolといったBaaSも登場してきていますし、Serverを用意する敷居が大分下がってきていると思っています。一方、Falcorに目を向けると事情は大分違っていて、falcor-express をNode.jsのExpress上に載せてエンドポイントサーバを作る以外の手段が無いように見えます。
GraphQLを触りながらも「もう少しFalcorのEndpoint Serverを用意する方法が色々あっても良いのになぁ」というのが頭をよぎり、今回のエントリにいたりました。さすがに自分でBaaSを作るのは難しいですが、「Serverを管理せずにFalcorのEndpointを公開する」を主軸に考えてみました。
今日のテーマは AWS API Gateway + Lambda + Falcor です。
作るもののイメージ
ポンチ絵にすると下記のようになります。
AWS API GatewayでHTTP Requestを受け付け、Lambda上に用意したFalcor Routerをキックする構成です。
通常、FalcorのサーバはとRESTful Webサービスと異なり、単一のエンドポイントしか持ちません(図中の/model.json
)。Falcor RouterがRequestの内容に応じて、返却すべきJSON Graphのレスポンスを決定します。このあたりの話は以前に、Falcor入門 3日目 Falcor Routerでサーバサイドを実装してみるに書いたので詳細はこちらを参照してください。
出来上がったブツ
上述の構成を実現するために、今回はServerless Frameworkを選択しました。ServerlessはLambda, API Gatewayの煩わしい設定を纏めて管理出来るツールです。また、自分で作成したServerlessのprojectをNPMにpublishするだけで、projectの雛形が用意できるというのも良い点です。
というわけで、Falcorのstarter kitを作ってみました。
事前にServerless CLIが動作する環境を用意してください。といっても、Node.jsとaws CLIが既にinstallされている人であれば下記のコマンドを実行するだけです。
npm i -g serverless
Serverless導入方法の詳細については、 AWS Lambdaを活用したServerless Frameworkを触ってみるのエントリが分かりやすかったです。
さて、Serverless CLIが無事installできたら、次のコマンドで、serverless-falcor-starterの雛形からServerless Projectを作成することができます。
sls project install serverless-falcor-starter
profileやstageについて質問が飛んでくるので適宜入力してください。
続いてLambda 関数, API Gateway Endpointをそれぞれdeployします。
cd serverless-falcor-starter
sls function deploy
sls endpoint deploy --all
無事Endpointのdeployが成功すると、下記のような出力がコンソール上で得られているかと思います:
Serverless: Successfully deployed endpoints in "dev" to the following regions:
Serverless: us-east-1 ------------------------
Serverless: GET - model - https://xxx.yyy.zzz.amazonaws.com/dev/model
Serverless: POST - model - https://xxx.yyy.zzz.amazonaws.com/dev/model
Serverless: OPTIONS - model - https://xxx.yyy.zzz.amazonaws.com/dev/model
この段階で、https://xxx.yyy.zzzz.amazonaws.com/dev/model
のURLにFalcorのEndpointがdeployされています。model.getValue("greeting")
に相当するGETリクエストを叩ける猛者がいれば、cUrlやPostmanで試してみるのもありでしょう。
今回作成したstarter kitには、Client側のsampleも同梱しています。client-sample/config.json
をエディタで開き、"endpoint"
キーの値を、AWS API Gateway Endpoint URL(e.g. https://xxx.yyy.zzz.amazonaws.com/dev/model
) に書き換えます。
{
"endpoint": "Replace the endpoint URL of your AWS API Gateway"
}
次いで、下記のコマンドを実行してブラウザからhttp://localhost:8080
を開いてください。
cd client-sample/
npm i && npm start
下図の画面が立ち上がり、このstarter kitで作成したFalcor Endpointとの通信を確認できます。
中身の説明
すこし、serverless-falcor-starterの中身の説明を書いておきます。
LambdaとFalcor DataSource, Router
下記がLambdaに登録する関数です。先述の通り、Falcor ServerがSingle Endpointな構成となることから、LambdaのFunctionはこのhandler唯1つとなります。
import dataSourceHanlder from "falcor-lambda";
import MyRouter from "./router";
module.exports.handler = dataSourceHanlder(() => new MyRouter(), {debug: true});
dataSourceHanlder
はFalcorのDataSourceをLambdaのhandlerとして登録するためのhelper関数です。「FalcorのDataSourceからWeb Server用のcontrollerを作成する」という意味において、 falcor-express と同等の役割を担うため、こいつを参考にして falcor-lambda を作成しました。
DataSourceになるのは下記のFalor Routerを継承して作成したClassです。starter kit用のsampleなので軽めにget/setだけ用意しました。
import Router from "falcor-router";
export default class MyRouter extends Router.createClass([
{
route: "greeting",
get: function(pathset) {
return {
path: ["greeting"],
value: "Hello, world",
};
},
set: function(jsonGraph) {
return {
path: ["greeting"],
value: jsonGraph.greeting,
};
}
},
]) {
}
API GatewayのEndpoint
HTTP Requestを受け付けてLambda Functionを呼びだすのはAPI Gatewayの役割です。今回のようにServerless Framwworkを使った場合、s-function.json
にその設定が記述されます。
Client SideからFalcor Http Data Sourceを使ってリクエストを飛ばした場合、GET/POSTの両方が飛んでくるため、双方の設定を用意しています。Requestに含まれるパラメータ(paths
等)はrequestTemplatesを定義してLambdaのeventに変換しています。
"endpoints": [
{
"path": "model",
"method": "GET",
"type": "AWS",
"authorizationType": "none",
"authorizerFunction": false,
"apiKeyRequired": false,
"requestParameters": {},
"requestTemplates": {
"application/json": {
"method": "$input.params().querystring.method",
"paths": "$util.escapeJavaScript($input.params().querystring.paths)"
}
},
"responses": {
"400": {
"statusCode": "400"
},
"default": {
"statusCode": "200",
"responseParameters": {},
"responseModels": {
"application/json;charset=UTF-8": "Empty"
},
"responseTemplates": {
"application/json;charset=UTF-8": ""
}
}
}
},
{
"path": "model",
"method": "POST",
"type": "AWS",
"authorizationType": "none",
"authorizerFunction": false,
"apiKeyRequired": false,
"requestParameters": {},
"requestTemplates": {
"application/json": {
"method": "$input.json('$.method')",
"jsonGraph": "$input.json('$.jsonGraph')",
"callPath": "$input.json('$.callPath')",
"arguments": "$input.json('$.arguments')",
"pathSuffixes": "$input.json('$.pathSuffixes')",
"paths": "$input.json('$.paths')"
}
},
"responses": {
"400": {
"statusCode": "400"
},
"default": {
"statusCode": "200",
"responseParameters": {},
"responseModels": {
"application/json;charset=UTF-8": "Empty"
},
"responseTemplates": {
"application/json;charset=UTF-8": ""
}
}
}
}
],
ところで、API GatewayでPOSTのEndpointを公開する場合、Content-Typeはapplication/json
一択なのですが、falcor-http-datasourceはx-www-form-urlencoded
のPOSTしか対応していませんでした。最初、API Gateway 側でx-www-form-urlencoded
からパラメータを取り出すtemplateを考えたのですが、HTMLフォームからAPIGatewayを使ってAWS LambdaにPOSTする 等を見るとかなり面倒な印象を受けたため挫折。 falcor-http-datasource に Content-Typeにapplication/json を指定できるようにするPR を送って対応しました。
結果、Client SideでDataSource作成時に下記のように書くだけで、API Gatewayに接続できるようになっています。
import { Model } from "falcor";
import HttpDataSource from "falcor-http-datasource";
const url = require("../config.json").endpoint;
const source = new HttpDataSource(url, {
/* The following options are required for CORS request */
crossDomain: true, withCredentials: false,
/* An AWS API Gateway endpoint allows only application/json Content-Type */
headers: {
"Content-Type": "application/json",
},
});
const model = new Model({
source,
});
おわりに
今回のエントリでは以下を解説してきましたが、如何でしたでしょうか。
- AWS API Gateway + Lambda + Falcor Server 構成の概要
- Serverless Framework, serverless-falcor-starter を利用したProjectのセットアップ方法
- API Gateway, Lambda上でFalcorを扱う場合の諸注意
今回作成したProjectでは、Falcor Routerの先にどのようなデータストアを配置するかは考えていませんが、AWSですので、DynamoDBを使う、なんてのが考えられます。
実際、GraphQL + Serverlessのprojectとしてserverless-graphql-blog というのがあるのですが、GraphQLのSchema Resolver(FalcorのRouter相当)からDynamoDBを呼びだしてデータの永続化をしていました。
興味がある人は、是非Lambda + Falcor Router + DynamoDB の構成にチャレンジしてみては如何でしょう。
それでは、また。