Falcorで実現する効率的なfetch サーバー編 第1回 サーバーサイド実装の概要
Falcorを使うにあたって必要となるサーバーサイド側の実装を解説します。まずは、サーバーサイド実装のあらましを、単純なPathのリクエスト応える実装を行いながら確認しましょう。
- カテゴリー
- Node.js
発行
この記事の内容は古くなっています。また、Falcorは3年前から開発が止まっており、現在ではおすすめできるライブラリではなくなりました。現在はGraphQLで実現できることも多いので「Hygraph(GraphCMS)で学ぶGraphQL」などを参照してください。(2024.10.15)
はじめに
このシリーズではFalcorを使うにあたって必要となるサーバーサイド側の実装を取り上げていきます。
Falcorの基本的な考え方やクライアント側の実装については以前のシリーズ*でも扱っているので、そちらも目を通しておくことをおすすめします。
*注:Falcorのクライアントサイドの実装がわかるシリーズ
- 「Falcorで実現する効率的なfetch」シリーズ
サーバーサイドでやること
Falcorを使った場合、クライアント側ではPathやPathSet*と呼ばれるFalcor用のクエリを書いてデータの取得や更新、追加、その他の操作を行います。
*注:PathやPathSet
Path、PathSetについては過去の記事でも紹介しています。
Falcorを使ってデータを取得する例
const model = new falcor.Model({
source: new falcor.HttpDataSource('/model.json')
});
model.get('hello').then(res => {
console.log(res);
});
このとき、ブラウザのコンソール上では次のような結果になります。
ブラウザのコンソール上で確認できる結果
{
"json": {
"hello": "hello!"
}
}
このコードが実行されているとき、HTTP上では/model.json
というURLのパスに対してpaths
とmethod
というパラメータを持ったHTTPのGETリクエストが送られています。
実際に送られるリクエスト
GET /model.json?paths=%5B%5B%22hello%22%5D%5D&method=get HTTP/1.1
それに対して返ってくるHTTPレスポンスは次のようになっています。サーバー側では上記のGETリクエストをハンドリングして、下記のようなレスポンスを返せるようにしなくてはなりません。
実際に返ってくるレスポンス
{"jsonGraph":{"hello":"hello!"}}
クライアント側のFalcorライブラリが生成するHTTPリクエストはとても複雑で、これをハンドリングして正しいJSONレスポンスを返せと言われても大変です。ただ、その実装については、サーバーサイド側でリクエストのパラメータをパースするライブラリなどもnpmで公開されているので、いくらかは仕事を楽にしてくれます。
そうしたライブラリを利用したサーバーサイド側の実装例をこのシリーズでは紹介していきたいと思っていますが、まずはサーバーサイド側の概要を見ていきましょう。
サーバーサイドに必要なもの
Netflixが公開している、サーバーサイドで利用するためのFalcor関連ライブラリは2種類あります。
- falcor-router:サーバーサイド側でのData Source*実装を支援するライブラリ
- falcor-express:WebフレームワークであるExpressにFalcorのData Sourceを載せられるようにするミドルウェア
*注:Data Source
Data SourceはFalcorクライアントからのリクエストに対して実際にデータ(JSON Graph)を返す役割を持ちます。過去の記事でも紹介をしています。
厳密なイメージではないが、役割としてはfalcor-routerはデータを返すData Sourceの実装部分で、それとExpressを繋いでくれるのがfalcor-express。
この2つのライブラリは両方とも必須となるでしょう。falcor-routerは後述するfalcor-expressから受け取ったリクエストを元にして構築したデータを、最終的にExpressのレスポンスとして正しく送り返すため、まずはリクエストを繋いでくれたfalcor-expressへ手渡すためのインターフェイスを実装するために使います。
falcor-expressは、クライアント側で生成されるFalcor用のパラメータが付いたHTTPリクエストをパースしてfalcor-router側に渡した後、その処理結果を受け取り次第、Expressに戻してあげる役割を持っています。
このfalcor-expressと同じ役割で他のWebフレームワークに対応したものとして、hapi用のfalcor-hapiや、restify用のfalcor-restifyもあります。このシリーズではExpressを使うことを前提として進めていきます。
コラム:Node.jsは必須か?
いまのところ、こうしたライブラリはnpmで公開されており、Falcorのサーバーサイド側実装を行うためにはNode.jsを選択したほうがよいということになります。
これは、必ずしもサーバーサイドのすべてをNode.jsで実装すべきということを意味するものではありません。
たとえば、すでにRubyなどの他の言語でREST APIが実装済みであるのなら、これをそのまま利用して、既存のサーバーサイドとクライアントサイドの間にFalcorサーバーを配置するという方法もあります。
この場合、Falcorサーバーはフロントエンドのためのバックエンドとして働きます。クライアント側では、Falcorサーバーへのリクエストを行い、Falcorサーバーはまた別のAPIへリクエストを行ってデータを返します。
Expressのセットアップ
それでは、Express*のセットアップを進めていきましょう。本記事はFalcorのシリーズなので、Express自体については深くは触れません。Expressを動かすためのコードは必要最低限のものしか登場しませんが、考え方、仕組みなど、Expressについてある程度知っておくこともおすすめします。
*注:Express
Expressは過去のCodeGrid記事でも紹介をしていますので、参考にしてください。
サンプルリポジトリ
今回紹介する最終的なコードは以下より入手できます。
Falcorサーバーサイド サンプルリポジトリ
サンプルコードはNode.jsのバージョン4.4.5で動作を確認しています。また、ブラウザとそのバージョンはChrome 51、Firefox 47での動作を確認しています。
最低限のExpressサーバー
Falcor用のコードは何も入っていない、Expressのサーバーを立ち上げるだけのコードです。これをベースにしてfalcor-express、falcor-routerを繋いでいきます。
Expressの他にbody-parserも入っていますが、これはFalcorクライアントのリクエストパラメータを読み取るために必要となります。
server.js
'use strict';
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.urlencoded({extended: true}));
app.use(express.static('./'));
const server = app.listen((process.env.PORT || 3000), (err) => {
if (err) {
console.error(err);
return;
}
console.log(`app start, on port ${server.address().port} in ${app.get('env')} mode ${Date().toString()}`);
});
falcor-express
falcor-expressはExpressのmiddleware*として機能します。
*注:middleware
ここでいうmiddlewareとは、リクエストに対する処理をプラグインのように追加できるExpressの機能です。npmでいろいろなmiddlewareが公開されており、falcor-expressもそうしたmiddlewareのうちのひとつです。
次のようにmiddlewareをマウントするapp.use
メソッドを使ってfalcor-expressをExpressにマウントします。このときに、falcor-expressの初期化も行う必要があります。そのためにはfalcorExpress.dataSourceRoute
メソッドを呼び出します。
falcorExpress.dataSourceRoute
メソッドは関数を引数として受け取ります。
その関数内でやらないといけないのは、クライアントからのリクエストに応えるインターフェースを持ったData Sourceをreturn
することです。こうすることで、ExpressとData Sourceはfalcor-express middlewareの助けによって繋がることができます。
その結果として、ExpressがFalcorクライアントに応答できるようになります(以下のサンプルコードではこの部分はまだTODO
の扱いになっています)。
falcor-expressのマウント
const falcorExpress = require('falcor-express');
// 中略
app.use(bodyParser.urlencoded({extended: true}));
app.use('/model.json', falcorExpress.dataSourceRoute((req, res) => {
// TODO: FalcorRouterインスタンスを返す
}));
app.use(express.static('./'));
// 後略
falcor-router
次はfalcor-routerの準備です。一緒に簡単な読み取り用の値も返せるような実装もしてみましょう。
falcor-routerの準備
const FalcorRouter = require('falcor-router');
// 中略
app.use(bodyParser.urlencoded({extended: true}));
app.use('/model.json', falcorExpress.dataSourceRoute((req, res) => {
return new FalcorRouter([
{
route: 'hello',
get() {
return {
path: ['hello'],
value: 'hello!'
};
}
}
]);
}));
app.use(express.static('./'));
// 後略
ひとつ前のコード例で// TODO
にしていた部分を書き換えました。return new FalcorRouter
以下です。ここではFalcorRouterインスタンスを初期化してreturn
しています。渡している引数はデータの在り処であるPathを示すroute
とそれに対するget
、set
、call
いずれかのアクションを定義したオブジェクトの配列です。
上記のコードではhello
というキーの取得にのみ対応した処理をひとつだけ定義しています。これでクライアント側のmodel.get('hello')
というリクエストに対応できるようになるわけです。
get
メソッドの中では、最終的にpath
とvalue
を持ったオブジェクトを返す必要があります。
path
には返す値がJSON Graph内のどこに存在しているかを示したPathを渡します。hello
というキーのリクエストに対応するので、ここはhello
になります。また、配列書式でのPathを渡すことが必須となっているので、**['entries', 0, 'title']
のような階層指定がない場合でも必ず['hello']
のような配列書式での指定をします**。
そしてvalue
にはそのPathに存在する値を指定します。
完成したコード全体は次のようになっています。
- ソースコード:server.js
このroute
とアクションの定義をたくさん書いていくことによって、豊かなデータを提供するData Sourceを実装することができます。
Expressの動作確認
これで最低限の実装が終わりました。クライアント側から動作確認してみましょう。
先ほどhelloというPathのデータ取得に対応できるようにしたので、それをリクエストしてみます。
client.js
const model = new falcor.Model({
source: new falcor.HttpDataSource('/model.json')
});
model.get('hello').then(res => {
console.log(JSON.stringify(res, null, 2));
});
次のようなデータが返ってきたら成功です。
コンソールの結果
{
"json": {
"hello": "hello!"
}
}
まとめ
今回はFalcorのサーバーサイド側実装の概要と、単純なPathのリクエストに対応する例を紹介しました。
Falcorのクライアントサイド側を扱っていたシリーズでも解説したように、Pathは実際にはmodel.get(['entries', 0, 'title'])
のように任意のインデックス値を指定できたりもします。
そうしたPathへの対応も、次回紹介していきたいと思います。