LoginSignup
0
0

More than 5 years have passed since last update.

Netflix Falcorについて(3)

Last updated at Posted at 2017-08-06

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を含みます。ブログのエントリーとでも考えてください。

initData.js
[
  {
    title: '夏休み1日目',
    content: '1日目は東武動物公園のプールに行った'
  },
  {
    title: '夏休み2日目',
    content: '2日目は森林公園に行ってセミをとった'
  }
]

 目指すのはクライアント側から Virtual JSON のデータ要求があった時に、サーバ側で、Falcor Routerがその Virtual JSON を創り出し返すことです。今回の例は、Virtual性が低いのですが、後に同じ枠組みでVirtual性の高いものにしていくつもりです。サーバ側のコードは以下のようです。Falcor Routerで2個のrouteを定義しています。

index.js
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を創り出すかは、プログラマが設計する必要があります。仕様が決まれば、クライアント側のコーディングは格段に楽になります。

index.html
<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を掲載しておきます。

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"
  }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0