Netflix Falcorについて(1)
Netflix Falcorについて(2)
Netflix Falcorについて(3)
Netflix Falcorについて(4)
Netflix Falcorについて(5)
今回はサーバ側でfalor-routerを使ってDataSourceを実装してみます。まずはサーバ側の設定を以下の手順で行います。
mkdir falcor-qiita3
cd falcor-qiita3
npm init
npm install express --save
npm install falcor-express --save
npm install falcor-router --save
npm install mongoose --save
mongoimport --db qiita3db --collection articles --jsonArray initData.js --host=127.0.0.1
node index.js
initData.jsはMongoDBの初期データで以下のように2つのcollectionを含みます。ブログのエントリーとでも考えてください。
[
{
title: '夏休み1日目',
content: '1日目は東武動物公園のプールに行った'
},
{
title: '夏休み2日目',
content: '2日目は森林公園に行ってセミをとった'
}
]
目指すのはクライアント側から Virtual JSON のデータ要求があった時に、サーバ側で、Falcor Routerがその Virtual JSON を創り出し返すことです。今回の例は、Virtual性が低いのですが、後に同じ枠組みでVirtual性の高いものにしていくつもりです。サーバ側のコードは以下のようです。Falcor Routerで2個のrouteを定義しています。
var falcorExpress = require('falcor-express');
var Router = require('falcor-router');
var express = require('express');
var app = express();
//---------- 以下はMongoDB & Mongooseのお決まりの設定です
var mongoose = require('mongoose');
var databaseUri = 'mongodb://localhost/qiita3db';
mongoose.Promise = global.Promise;
mongoose.connect(databaseUri, { useMongoClient: true })
const articleSchema = {
title:String,
content:String
};
const Article = mongoose.model('Article', articleSchema, 'articles');
//----------
// expressにおいてfalcor-routerを使ってDataSourceを実装しています
app.use('/model.json', falcorExpress.dataSourceRoute(function (req, res) {
// Routerのハンドラーで Virtual JSON resourceを作りreturnします
return new Router([
// 1個目のroute定義
{
route: 'articles.length',
get: () => { return Article.count({}, (err, count) => count)
.then ((articlesCount) => {
return {
path: ['articles', 'length'],
value: articlesCount
};
})
}
},
// 2個目のroute定義
{
route: 'articles[{integers}]["_id","title","content"]',
get: (pathSet) => {
const index = pathSet[1];
return Article.find({}, (err, docs) => docs)
.then ((docsArray) => {
var results = [];
index.forEach((index) => {
const singleObject = docsArray[index].toObject();
const result = {
path: ['articles', index],
value: singleObject
};
results.push(result);
});
return results;
});
}
}
]);
}));
// serve static files from current directory
app.use(express.static(__dirname + '/'));
var server = app.listen(3000);
それぞれのrouteに対応する要求をクライアント側から発行します。クライアント側のコードは以下のようになります。サーバ側の1個目のroute定義とクライアント側の1個目のパスが対応します。2個目のroute定義と2個目のパスが対応します。サーバ側はクライアントの要求に応じた結果(JSON Graphの一部)を創り出し、returnします。ここで注意ですが、routeのgetハンドラーはPromise( or Observable)をreturnします。例えば2個目のrouteハンドラーは「return Article.find({}, ...」とreturnしていて、MongooseのPromiseを返しています。(ただし非同期処理を含まない場合は静的な値をreturnして良い)。クライアント側でどのようなパスを発行して、サーバ側でどのようなJSON Graphを創り出すかは、プログラマが設計する必要があります。仕様が決まれば、クライアント側のコーディングは格段に楽になります。
<html>
<head>
<!-- Do _not_ rely on this URL in production. Use only during development. -->
<script src="//netflix.github.io/falcor/build/falcor.browser.js"></script>
<script>
var model = new falcor.Model({source: new falcor.HttpDataSource('/model.json') });
var length=0;
async function fetch() {
await model. // 1個目のパス
get("articles.length").
then(function(response) {
var res = JSON.stringify(response,null,2);
console.log(res);
document.write("<div>"+res+"</div>");
length = response.json.articles.length;
});
await model. // 2個目のパス
get(['articles', {from: 0, to: length-1}, ['_id','title', 'content']]).
then(function(articlesResponse) {
var res2 = JSON.stringify(articlesResponse,null,2);
console.log(res2);
document.write("<div>"+res2+"</div>");
});
}
fetch();
</script>
</head>
<body>
</body>
</html>
ブラウザでindex.htmlを開くと、以下のような実行結果がブラウザに表示されます。望みの結果をサーバ側から取得できました。
{ "json": { "articles": { "length": 2 } } }
{ "json": { "articles": { "0": { "_id": "5986bd440720b6845e72238c", "title": "夏休み1日目", "content": "1日目は東武動物公園のプールに行った" }, "1": { "_id": "5986bd440720b6845e72238d", "title": "夏休み2日目", "content": "2日目は森林公園に行ってセミをとった" } } } }
クライアント側の説明です。model.get()で2度modelにアクセスしていますが、最初のgetの結果(length)を2回目のget()で使用するので、同期をとって実行される必要があります。そのためここでは async/await を使っています。
最初のget()のパスは"articles.length"でarticleオブジェクトのlengthプロパティにアクセスしています。2番目のget()のパスは少し複雑です。
get(['articles', {from: 0, to: length-1}, ['_id','title', 'content']])
articles配列の、indexが0~length-1の要素の、それぞれ'_id','title', 'content'プロパティにアクセスすることを意味しています。このパスの指定はfalcorに特徴的なものです。取得するindexの範囲指定とプロパティの集合を明示的に指定します。必要以上の量のデータを取得するのを避けるためです。例えればSQLでselect文には必ずwhere句を付けるようにと、公式サイトでは説明されています。(この制限を避けるためには$atomを使う)
またgetメソッドはPromise(or Observable)を返しますので注意してください。thenで受けることもできますし、subscribeで受けることもできます。公式サイトではsubscribeの使用を勧めているようです。
今回の例は、JSON GraphのGraph性が感じられないものですが、次回以降にJSON Graphの説明に入っていきたいと思います。
最後にpackage.jsonを掲載しておきます。
{
"name": "falcor-qiita3",
"version": "1.0.0",
"description": "mkdir falcor-qiita3\r cd falcor-qiita3\r npm init",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.15.3",
"falcor-express": "^0.1.4",
"falcor-router": "^0.8.1",
"mongoose": "^4.11.5"
}
}