はじめてQiitaで記事を書いています。先日「Node.js + Express.js + Backbone.js」でサーバとクライアントの両方のシステムを構築する"Rendr"というウェブフレームワークを試しました。Rendrは、アメリカで人気のサービス"AirBnb"のチームが開発し、オープンソースとして公開したものです。クライアント側での採用事例は増えているBackbone.jsですが、サーバでの利用は珍しいのでは無いでしょうか。Rendr自体もまだ日本語での紹介例が無い事もあり、システムを紹介しつつ、使用感やメリット、そしてデメリットをメモしておこうと思います。
※2回目を書きました: JavaScript - Rendr入門(2): リクエスト処理の流れ(Router, Controller、View、Template)
#Rendrとは?
はじめにも書いたとおり、Rendrはairbnbのチームが開発したウェブフレームワーク(彼ら自身はフレームワークという言葉を避けて"ライブラリ"と呼んでいます)です。Node.js + Express.jsの上にミドルウェアとして構築されており、「Backbone.jsをサーバ側で(も)動かす」事を主眼としています。
- airbnb/rendr (Rendrのgithubリポジトリ)
https://github.com/airbnb/rendr
Rendrを開発した経緯は、airbnbの技術ブログで紹介されています。
- We’ve open sourced Rendr: Run your Backbone.js apps in the browser and Node.js - Airbnb Engineering
http://nerds.airbnb.com/weve-open-sourced-rendr-run-your-backbonejs-a/
元々airbnbのモバイル版を開発する際に作られたもので、
- 環境に依存しないアプリケーションロジックを書く。
-
if (server) {・・・} else {・・・}
形式のコードを最小化する。 - RESTful API
- フレームワークではなく、ライブラリ。
- ライブラリの複雑さを隠蔽する。
- サーバサイドのDOMを使わない。
- シンプルなExpressのミドルウェア
を目標にしています。
airbnbチームも元々はRails + Backbone.jsでウェブアプリを開発していたようですが、
- アプリケーションコードがサーバとクライアントで分割される。あるいは、同じコードが重複してしまう。
- サーバ側でのレンダリングをERBなどで行うと、テンプレートが重複する。
などの問題意識をもったようです。
では全てをクライアント側でレンダリングしてしまえば良いかというとそういうわけでも無く、後で紹介するプレゼンテーションの動画では「レスポンスをよくするために、サーバ側でもレンダリングして最初のHTMlを早く表示したい。しかし、インタラクティブであるためにクライアント側でもレンダリングをしたい」という背景が説明されています。
#サーバ・コード
Rendrを使ったアプリケーションの構造を知るには、下記のURLで公開されているスケルトンコードを読むのが良いと思います。
- airbnb/rendr-app-template (Rendrベースのスケルトンアプリ)
https://github.com/airbnb/rendr-app-template
「app」ディレクトリの下は下記の様な構成になっています。
- collections ・・・Backbone.Collection
- models ・・・ Backbone.Model
- views ・・・ Backbone.View
- templates ・・・ Handlebars(mustacheの機能強化版)ベースのテンプレート
- controllers ・・・Collection/Modelの生成とViewの呼び出しを行う
- app.js
- router.js
- routes.js
controllersはBackboneには無かった概念です。元々はRouterで行っていたアプリケーション・ロジックを、独自の拡張で吸収しています。Routerのコードが"fat"になりがちだったので分割したとのこと。
Controller、View、Templateはid('StatementListView')をベースに連携していて、ファイル名を同一にしておけば自動的に関連したものが呼び出される仕組みになっています。
例: statements_controller.js → views/statements/index.js → templates/statements/index.hbs
#クライアント・コード
コントローラでmodel/collectionを生成してテンプレートで描画を行うとHTMLが生成される訳ですが、Rendrが面白いのはこの部分からで、生成したHTMLからクライアント側でもBackbone.jsのコードがキックされ、Viewの生成やイベントバインディングが行われるようになっています。HTML中のビュー生成部分にはサーバ側で自動的にベースになったViewとデータIDが割り振られ、HTMLとViewのバインディングについてアプリコードで特に意識をする必要はありません。
サーバ側で全てのページのレンダリングを行いつつ、まるでクライアント側でBackbone.Viewを生成してレンダリングしたように以降の処理をクライアント側で引き継ぐことができるのです。繰り返しになりますが、コントローラ、ビュー、テンプレートシステムは、同一のコードがサーバとクライアント両方で動きます。
この連携を実現するために、RendrではBackbone.Viewのコードをオーバーライドしていて、Backbone.View#render()とBackbone.View#initialize()は予約されて使用されなくなっています。代わりにBackbone.View#postInitialize()とBackbone.View#postRender()にアプリコードを書きます。この点、従来のBackboneのコードがそのまま動くわけでは無いので注意が必要です。
Viewのメソッドは下記の様にサーバとクライアント両方で動くものと、クライアントのみで動くものに分けられています。
-
view.postInitialize()
・・・サーバ/クライアント両方で動く -
view.preRender()
・・・サーバ/クライアント両方で動く -
view.render()
・・・クライアントのみで動く -
view.postRender()
・・・クライアントのみで動く -
view.getTemplateData()
・・・サーバ/クライアント両方で動く
データの取得(サーバクライアントで共通のfetchメソッドを使う)とテンプレートへの流し込みをサーバ側で、イベントのバインディングとそこから呼び出されるハンドラ内のDOM操作をクライアント側でという切り分けになるかと思います。最初はどちらでどちらが動くか確認しつつ書く感じになりますが、慣れると特に意識をせずに「レンダリング前と、後」という振り分けができるようになります。この振り分けが、同じBackbone.Viewの中で行われます。
データのやりとり
サーバ側でレンダリングを行う際にコントローラで生成されたmodel/collectionは、HTMLにJSONとして埋め込まれてクライアント側の「初期値」として使われます。その後、例えば記事の一覧をアップデートする場合は、Backbone.Collectionに設定された「url」にアクセスが行われます。何も考えなければ、レンダリングを行ったサーバの「/api/-/コレクション・モデルの名前」にアクセスが行われます。
データの扱いは、server/lib/data_layer
で抽象化が行われ、サーバ側でのデータ取得とクライアントからのHTTP経由でのAPIコールが同一のコードで処理されます。data_layerは自前で実装する必要がありますが、サーバ内でのデータ処理とクライアントからのHTTPリクエストを透過的に記述できます。
サーバ側でCollectionを生成してHTMLをレンダリング、クライアントで「もっと見る」がクリックされたらCollection.fetchでさらにデータを取り込むというようなパターンが適用できます。
Stitchを使ったアセットパイプラインと、CommonJS形式の「require()」
サーバ側と共通のモジュール管理を実現するために、Rendrは'Stitch'というライブラリを使用しています。
- sstephenson/stitch
https://github.com/sstephenson/stitch
サーバ側で指定されたJavaScriptを連結してクライアントに配信することで、クライアント側JavaScriptでも var hoge = require (モジュール)
の形式でライブラリをロードできます。これにより、Node.js向けに書かれたサーバ側コードをクライアント側でも動かすことができます。
#使用感(メリット・デメリット )
ウェブサービスがリアルタイム化、インタラクティブ化し「ウェブアプリ化」が進む中、これまでのサーバを中心としたHTMLレンダリングシステムから、クライアント側JavaScriptを含めた、あるいは多くのコードがクライアント側に移行したシステムへの移行が進んでいます。こうした中で「どうせならサーバもJavaScriptで」と考えるケースも増えているのでは無いでしょうか?
筆者の場合も"Railsを中心に、クライアント側JavaScirptで一部書き換えを行う"システムから"RailsがほぼAPIサーバ化し、クライアント側JavaScriptに比重が移った”システムへと移り変わりを経験しました。その際に感じたのは、「クライアント側JavaScriptのコードを書きつつサーバ側も修正しようとすると言語のスイッチによるコストが高い」という事。APIコードの確認をしつつクライアントでの動作を調整する時などに違和感を感じていました。
Rendrは、サーバ/クライアントの言語の違いによる開発コストの増大を抑え、またサーバロジックとクライアントロジックを部分的に共通化することによる全体のコード量の減少にも貢献してくれます。
フレームワークとしてもBackbone.jsに全てが統一されることによる知識の集約化、学習コストの低減の効果があり、非常にコンパクトな開発ができたと思います。
デメリットをあげるとすると、まだ非常に若いプロジェクトで、もともとフルスタックを志向していない事もあり、足りない機能が多いことがあるでしょう。サンプルのプロジェクトは、データ取得を外部のHTTP APIに依存していてDBへの接続コードすらありません。コードのアップデートも頻繁で、あらゆるパートでドキュメントが足りていない状況です(開発者は非常にレスポンスが良く、github issuesに質問を立てると驚くようなスピードで返事が来ますが)。
たとえば今回自前で実装を行ったのは、下記の部分です。
- UserAgentでPC/モバイルの環境の違いをチェックして、レイアウトやテンプレートを切り替える。
- データを取得するためのサーバ側実装(server/lib/data_layer)を書き換えて、MySQLに接続してデータを渡すためのレイヤーを追加。
全体的な印象としては、開発チームのプレゼン資料やgithubのissuesなどに全て目を通して、それでようやく動きを理解できたという感じでした。
また、個人的にはこれが初のNode.jsベースの開発だったので、サーバの運用やデータベースへの接続コードの実装などありとあらゆる部分で試行錯誤が必要だったことも、デメリットと言えばデメリットでした。Rails + Backbone.js的な環境からの移行を考える方には、これもまた考慮が必要なポイントでしょう。
#まとめ
すでに実戦投入しておいてあれですが、まだ試行錯誤を続けている状況です。ベースの大部分がExpress.jsであることから、システムとしては安定していて問題の押さえ込みも比較的容易である印象です。Backbone.jsでの開発経験があり、テンプレートシステムとしてMustacheを使ってきていたので、その部分の新規学習コストが低かったこともありますが、スムーズに開発を軌道に乗せることもできました。
今回のプロジェクトでは、コードの一部をインターンの学生に手伝ってもらい、クライアント側のUIのパーツの開発を協力会社に依頼していましたが、Backbone.jsという共通言語を介して(自分が把握している限りは)非常にスムーズなやりとりができました。開発したプロダクトは、モバイル版の後にPC版を構築したのですが、デザイン段階からパーツの流用を意識していたことも有り、ほぼレイアウトとテンプレートの差し替えのみの作業で数日で完了することもできました。
なにより「コードをサーバクライアントで共通化する」という点がスマートに実現されていて、開発そのものが楽しくなった事を大きく評価しています。
安易に「おすすめ」をするものではありませんが、「ぜひ興味をもってもらいたい」プロダクトです。
(メモを残す意味で一気にこの文章を書きました。不足があればコメントしてください。ひょっとしたら続編でもう少し詳しく開発の実際について説明できるかもしれません。)
#ポインタ
その他Rendrに関する記事や、スタッフによるプレゼンがいくつか公開されています。
-
Introduction to Airbnb's Rendr - YouTube
http://www.youtube.com/watch?v=JmS0HrH15ZI -
Airbnb Open Sources Rendr, A Library For Running Backbone.js Apps On Both Client And Server | TechCrunch
http://techcrunch.com/2013/04/19/airbnb-open-sources-rendr-librar/ -
Our First Node.js App: Backbone on the Client and Server | Hacker News
https://news.ycombinator.com/item?id=5137859