Skip to content

Instantly share code, notes, and snippets.

Star You must be signed in to star a gist
Save tily/1362110 to your computer and use it in GitHub Desktop.
サバクラ両方で動く JavaScript の大規模開発を行うために

サバクラ両方で動く JavaScript の大規模開発を行うために

原文:Scaling Isomorphic Javascript Code (This is just for study, please contact me at tily05 atmark gmail.com if any problem.)

考えてみれば Model-View-Controller とか MVC ってよく聞くよね。実際どんなものか知ってる? 抽象的に言うなら「オブジェクト情報の保持されるグラフィック・システム (つまり、ラスターではないグラフィック。ゲームとか) 上に構築された、表示系を中心としたアプリケーションにおいて、主要な機能どうしの関わりをうまく分離すること」とでも言おうか。もう少し深く考えを押し進めてみれば、これは当然、他のさまざまなアプリケーションにもあてはまる言葉 (bucket term ?) だ。

過去に多くの開発コミュニティが MVC による解決案を提供し、それによってよくあるユースケースにうまく対処し、地位を築くことができた。例をあげるなら、Ruby や Python コミュニティは Rails や Django を作り、MVC アーキテクチャを実現した。

こうした手法は Java, Ruby, Python といった他の言語では容易に受け入れらてきたが、Node.js について十分ではない。理由は単純で、それはただ、JavaScript が今や isomorphic な言語であるからだ。isomorphic というのは「ソースコードのどの行 (もちろん注目すべき例外もあるが) をとってみても、クライアント・サーバーの両方で実行できる」ということを意味している。表面的には無害に見えるが、この特徴のせいで現状の MVC ベースのパターンでは解決できない課題がたくさんある。

この記事では、まず現存するパターンをいくつか取り上げ、いかにそのようなパターンに関する実装や心配事が言語や環境に関わらず普遍的なものとなり得たか、そのようなパターンがどうして真に "isomorphic" な Javascript のソースコードにはあまり適していないのかを述べる。そして結論として、新しいパターン "Resource-View-Presenter" について述べる。

目次

デザインパターンは、アプリケーションの開発にとってなくてはならないオマンマのような存在である。アプリケーションやその環境についてのさまざまの心配事をカプセル化し、うまくまとめてくれる。ブラウザとサーバの間でこんなにもさまざまの心配事があるからね:

  • View を作るとして、(たとえばサーバ側で) 一時的にしか存在しないのか、(たとえばブラウザ側で) 永続するべきものなのか?
  • View を作るとして、複数のユースケースやシナリオの間で再利用可能なものか?
  • View を作るとして、アプリケーション特有のタグやマークアップが使われているか?
  • ビジネスロジックをどこに記述すべきか? (Model なのか Controller なのか)
  • アプリケーションの持つ状態はどのように保持されアクセスされるのか?

現存するパターンを取り上げ、上記の問題がどのように解決されているのか見てみよう:

  • Model-View-Controller
  • Model2
  • Model-View Presenter and Model-View-ViewModel
  • モダンな JavaScript による実装
  • Real-time Implications
  • Introducing Resource-View-Presenter
  • Conclusion

MVC


Figure 1: Model-View-Controller

伝統的な Model-View-Controller のパターンは永続的な View と交換可能な Controller を前提としている。たとえば、ある View においてログインしているユーザが違えば、違う Controller のロジックを適用することができる。抽象化されているので、View がどのように描画されるかについて判断を下す必要はない。(つまり、どんなテンプレートエンジンを使うのか、とか)

View が永続し、ユーザとのインタラクションは必ず View の中に入ってくるとしたら、MVC はフロントエンドの開発にとって非常に役立つパターンだ。あとで説明するが、実際このパターンを少し修正したものを Backbone.js は利用している。

Model2


Figure 2: Model2 Model-View-Controller

Model2 というパターンなんて聞いたことがないと心配しなくても大丈夫。これはさかのぼること 1999 年に Govind Seshadri が書いた "Understanding JavaServer Pages Model 2 architecture" という記事の中で発明されたデザインパターンだ。Model2 は必ずしも MVC パターンを必要としないという議論も可能だが、Ruby on Rails 等の一もっともモダンな実装は MVC を利用して Model2 に形を与えている。


Figure 3: Rails Model2 Model-View-Controller

共通認識として、Ruby on Rails のような Model2 っぽいフレームワークを使う場合、"重い Model と軽い Controller" にするべきとされている。すべてのアプリケーションに当てはまるわけではないが、実際に著者が見てきたアプリケーションもだいたいそうだった。伝統的な MVC の Controller の中では View からの入力を注意深く監視して反応するためにビジネスロジックで重くなりがちなので、この決定は納得のいくように見える。

HTTP のステートレスな性質を考えると、Model2 の View は本当に一時的なものだ。**どんな状態もリクエストさえまたいでしまえばビュー自体に保持されることはない。**ほとんどのサーバサイドのフレームワークでは、どんなアプリケーションの状態もセッション・クッキーを通して保存されている。だから Controller から View へは一方通行でしか情報を伝えられなくしたのは非常に論理的だが、フロントエンドの開発にとってはあまり適していない。

MVP と MVVM

Model-View-Presenter パターンと Model-View-ViewModel パターンは両方とも伝統的な MVC パターンに似ているが、いくつかの重要なちがいがある:

  • View は Model を直接参照できない
  • Presenter (または ViewModel) が View への参照をもち、Model が変更されたら View を更新する

MVPパターンは Martin Fowler によって詳細に論じられており (これこれ)、だいたいは 2 つの実装上の観点から論じられている:

  • 受動的な View:設計上、View はできるかぎり素朴であるべきで、絶対的で必要とされる表示 (presentation) やビジネスロジックは Presenter に含まれるべきだ
  • 監督者としての Controller:View で何か宣言すればそれが単純なロジックも内包しているようなシステムで利用される。このようなおまじないの中では、View の中の宣言的なロジックがシステム要件を満たすことができないときだけ、Presenter が処理を引き継ぐ


Figure 4: Model-View-Presenter


Figure 5: Model-View-ViewModel

MVP と MVVM はほとんど区別がつかないが、1 つの重要な例外がある:MVVM は ViewModel 内の変化が頑丈なデータ・バインディング・エンジンによって View に反映されることを前提としているのだ。Niraj Bhatt がこの違いについて「MVC vs. MVP vs. MVVM」という記事の中でうまく説明している:「たとえば古典的な MVP において View が IsChecked というプロパティをもっていて Presenter がそれを set しようとしているとすると、MMVM だったら ViewModel が IsChecked プロパティをもち、View がそれに同期しようとするだろう。」

MVP や MVVM にとっての望ましいゴールは、Presenter (または ViewModel) が簡単にユニットテストできることだ。というのみ、View の状態が必ず MVP では Presenter が呼び出すメソッドの中に含まれているし、MVVM では ViewlModel に set されるプロパティに含まれているから。

フロントエンドの開発を行うとき、このようなパターンは選択肢として完全にアリだ。ルーティングを行うレイヤーは制御を適切な Presenter (または ViewModel) に委ねることができ、今度は Presenter (または ViewModel) がブラウザの中で永続的な View に更新をかけたり反応したりすることができる。ちょっと修正すれば、これはサーバサイドでもうまく使えるようになる。理由としては単に Model と View がまったく連結していないからだ。 これによって一時的な View を Presenter (または ViewModel) が描画することができる。あとから説明するように、この微修正されたパターンは真に isomorphic となりうる。

モダンな JavaScript による実装

今まで述べてきたデザインパターンには、今日ではモダンな実装がたくさん存在している:

このようなフレームワークはふつうは 1 ページのアプリケーションを作るために使う (もっと伝統的に AJAX を使うことも可能だが) 。1 ページのアプリケーションの中におけるユーザ・インタラクションには 2 つのはっきりとした特徴がある。

  • OnHashChange または pushState イベント:ブラウザの URL が変わったときに発生する。たとえば http://myapp.local/#/some-page に移動したとき
  • DOM イベント:ユーザが現在の DOM ドキュメントに対して特定のインタラクションを行ったときに発生する。たとえば a タグをクリックしたとき

このようなライブラリで利用されているパターンやアーキテクチャについていくつか考えてみよう。興味のある向きは Peter Michaux の "MVC Architecture for Javascript Applications" という記事 を読んでみるといい。

Backbone

Backbone.js は今出ている中ではかなり人気のあるクライアントサイドの開発フレームワークだ。核心部分では伝統的な Model-View-Controller パターンが実装されている。しかし、もっと細かく調べてみると、上記で述べてきた伝統的な MVC パターンとはいくつか異なる点もある。


Figure 6: Backbone Model-View-Controller

上のダイアグラムでは OnHashChange イベントや Dom イベントの表す制御フローを分離して、Backbone の提供する別々のエントリーポイントを図示してみた。このニュアンスがうまく伝われば、Backbone と伝統的な MVC との間に 1 つの重要なちがいがあることがはっきりとするだろう:View が Model を操作するのだ。Backbone の Todo サンプルのソースコードを読んでみると、これが共通認識として受け入れられていることがはっきりと分かるだろう:

window.AppView = Backbone.View.extend({
  // ....

  //  
  // ToDoView が new されたら ToDo の Model のインスタンスに引き渡される
  //  
  addOne: function(todo) {
    var view = new TodoView({model: todo});
    this.$("#todo-list").append(view.render().el);
  }   

  // ....
}); 

window.TodoView = Backbone.View.extend({
  // ....

  //  
  // ここでは TodoView が Todo model の状態を更新している
  // これは伝統的な MVC からかけ離れており、View だけが Model への変更を
  // LISTEN することができる
  //  
  toggleDone: function() {
    this.model.toggle();
  }   

  // ....
});

伝統的な MVC パターンとは決別するという決断によって、規模の大きい Backbone アプリケーションはみんな同じような印象になるだろう:Controller のコードは少なく、モデルは密接に重い View と結びついている。このようなビジネスロジックの多く重い View は客観的に見れば Presenter と変わらないだろう。大規模な Backbone のコードベースでは、jQuery や zepto のようなDOM フレームワークでつなぎ合わされたたくさんのビューが存在すると思ったほうがいい。

伝統的な MVC パターンと決別することは何も悪くない。フロントエンドを開発するという意味では、View が Model を参照できると、アプリケーションから大量の bookkeeping なロジックを省くことができる。しかしながら、パターンは isomorphic ではないものとなってしまう。

Batman

Batman.js は JSConff 2011 でプレゼンされた新しい JavasScript フレームワークだ。Batman が内包する実体は Model, View, Controller なのだが、強力なデータバインディングエンジンと純粋な HTML View が存在するから、実際には Model-View-ViewModel の実装だということが分かる。


Figure 7: Batman Model-View-ViewModel

Batman であんまり作業をしたことがないから、自信を持って大規模な Batman アプリケーションのコードがどんな風になるかは分からない。Batman のドキュメントにはこう書かれていた:バインディングエンジンと軽量な View があるから、アプリケーション内のビジネスロジックは結局のところ Controller と Model にまたがることとなるだろう。

Backbone と同じように、Batman は伝統的な Model-View-ViewModel パターンと決別し、Model が直接 View とやりとりし、ViewModel (つまりは Controller) は View を直接操作しないようになっている。さらに、Model/View 間で互いに参照できるので、サーバサイドのパターンとしては容易に再利用することができない。しかしながら、もしも Composite パターンまたは Adapter パターンが Model のレイヤーに実装され、静的な View を描画したり、リアルタイムなリクエストに応答できるようになるならば、もっと簡単にサーバサイドのパターンに組み込むことができるだろう。

Real-time implications

ここ最近、開発者の間に広まるバズワードの中でもリアルタイムなウェブ・アプリケーションというのはとりわけ目立ちつづけている。Nodejitsu は今後予定されているポートランドの Keeping it Realtime カンファレンスの公式スポンサーである (チェックしてみてね!)。今まで見てきたパターンのうちのいくつかは十分にリアルタイムなサポート (WebSocket とか) をできるので見てみよう。

  • Model-View-Controller (いける):Model(s) がリアルタイムなイベントを監視し、適切に View を更新することができる。
  • Model2 (ダメ):一時的な View という考え方が Model2 には刷り込まれている。すなわち、Controller(s) は Model からのイベントを監視することができないのだ
  • Model-View-Presenter (いける):Model(s) はリアルタイムなイベントを監視することができるし、それを Presenter に伝えて適切に View を更新してもらうことができる
  • Model-View-ViewModel (いける):Model(s) はリアルタイムなイベントを監視することができるし、それを ViewModel に伝えて適切に View を更新してもらうことができる

このように MVC, MVP, MVVM は相性がよく、結果として Backbone.js や Batman.js はフロントエンドの開発におけるリアルタイムなフレームークとして役に立つことができる。だがサーバサイドでは必ずしもうまく行かない:伝統的な MVC, MVP, MVVM のパターンは View と Model が密接にかかわり合っているため、静的な View には役に立たないのだ。そこで Resource-View-Presenter の出番である。

Resource-View-Presenter を導入する

今まで見てきたように、MVC, MVP, MVVM パターンはみな、クライアントとサーバの両方ではうまく行かない。Resource-View-Presenter の重要なポイントは、どんなパターンも修正なしではクライアントとサーバの両方で完璧にうまく行くことはないだろう、と気づいたことだ。MVP・MVVM パターンを取り上げたときに指摘したように、MVP・MVVM では Model と View のレイヤーが分離されているため、パターンを真に isomorphic なものへと変更することが可能である。

Resource-View-Presenter が行った決断とは:

  • Model と View を分離する
  • クライアントとサーバのちがいを認識し、準備する
  • View は軽く、Presenter と Resource を重く
  • Presenter より Resource にビジネスロジックを書く
  • 一時的な View (静的なサーバサイドの View) と永続的な View (DOM) の両方が使えるようにする
  • ViewModel よりも Presenter にマークアップ (HTML) の純粋さを保持する
  • 永続する Presenter と Model を前提とする

上記の決定はテキトーに見えるかもしれないが、それぞれにちゃんと目的がある:

  • Model と Model を分離することで一時的な View と永続的な View の両方を扱うことができる
  • View を軽くすることで weld や mustache のようなモダンなロジックの少ないテンプレートエンジンとうまくやっていける
  • ViewModel の代わりに Presenter を採用することで、weld のようなデザイナーフレンドリーなテンプレートとうまくやっていける
  • クライアント・サーバの両方で Presenter と Model が永続的であることを前提としているので、両サイドで Presenter 内のリアルタイム機能をカプセル化することができる

もっと考えを進めてみよう。クライアントサイドでは Resource-View-Presenter は伝統的な MVP パターンに似ている。Model を Resouce に改名したのは、ビジネスロジックが Presenter より Resource に書かれるべきだという前提のあることが大きい。

このため、RVP における Resource は Model2 の重い Model のほうに似ている。術語的には伝統的な MVP の Model に似ているように思われるのだが (??)。

RVP を実装する際に、そのロジックが Presenter に書かれるべきかどうかを判断するためには、2 段構えのリトマス試験紙が必要となる。どんな Presentation 中心のロジックでも 「軽い」View には重すぎるし、結局はグローバルなアプリケーションの状態を利用するビジネスロジックには View は重すぎるのだ。

Backbone や Batman のように、クライアントサイドにおける RVP 実装は OnHashChange / pushState によるルーティングと DOM イベントの両方をサポートするべきである。


Figure 8: Client-side Resource-View-Presenter

サーバサイドでも Resource-View-Presenter はほぼ同じだ。ただ一つ違うのは、View が一時的であり、Presenter に対して呼び出しを行ったり参照したりはしないということだ。実際、JSON ベースのウェブサービスに RVP を使うなら、View はほぼ存在しないと言ってよい。ただ JSON.stringify() を呼び出せばよいのだから。


Figure 9: Server-side Resource-View-Presenter

ぱっと見では、サーバサイドの RVP は Model2 MVC に似ているように見えるかもしれないが、Presenter と Model の永続性がリアルタイムなイベントをサポートすることができるので、そんなに似てはいないことがわかるだろう。RVP はこれを行うために、モデル上のイベントや変更を監視する、そのイベントや変更というのは裏側では Redis PubSub や CouchDB といったリアルタイムなデータソースの変更によって行われている。

リアルタイムのサポートは特に注目に値する。なぜなら、アプリケーションの開発者が裏側のネットワーク転送ではなく、ビジネスロジックに専念することができるからだ。ささいなことに見えるかもしれないが、Express や Socket.io の提供するパターン (やパターンの欠如) を見てみれば (Express と Socket.io はそれぞれかなり人気のあり node.js のフレームワークとリアルタイム IO のライブラリである)、それが重要であることがはっきりと分かるだろう。


Figure 10: Express and Socket.io

こんなことを書いたからって Express や Socket.io を攻撃しようとしているわけではなく、彼らはできることをきちんと明示しているし、できることに関してはかなりうまくやっている。彼らはもっと高レベルな問題を扱っているので、デザインパターンは問題にならないのだ。

結論

大規模なアプリケーションを書くのはむずかしい。そんな中で、クライアント-サーバをまたいでコンポーネントをカプセル化し再利用するのはさらにむずかしい。今まで述べた分析を読んでくれたあとだから明白になっていればいいなと思うのだが、あなたのアプリケーションの中で Resource-View-Presenter パターンを利用すれば、これを簡単にできる。

自分で実装する時間がないって? 問題ないよ! この記事を書くことができたのは、Nodejitsu が設立当初から行ってきたおびただしいオープンソース活動を行う上で、注意深く考え勤勉に努力してきたからだ。私たちの哲学はずっと変わらないままだ:最高のツールさえつくれば最高のシステムはついてくる

きっとついてくると思っている。もうお気づきのこととは思うが、私たちは素晴らしいオープンソース製品のリリースを行おうとしており、そこでは Resource-View-Presenter パターンが実装されている予定だ。だから、もし何かフィードバックがあれば教えてほしい。そしてお見逃しなく、9/9 に行われるスウェーデン、ストックホルムの SWDC 2011 でこのリリースを発表する予定だ。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment