WebJourney の Component について書いてみる

コメント欄ではもったいないのでこちらに。

WebJourney のコンポーネントは、小さなRailsアプリケーションそのもの、です。基本的にはRails 1.X時代のコンポーネントの配置規約をベースにしていますが、ベースにしているのは配置規約だけです。

Rails 1.X のコンポーネントと何が違うかといえば、render_component 系を含む、render メソッドによる、Rails内でのコンポーネントの再利用を全くサポートしていない、その代わり、コンポーネント自体がURIを自由に使えるようにしている、これにつきると思います。これは、Rails コンポーネントの最大の欠点である、リクエストの再シュミレーションによるパフォーマンス劣化を問題視したためです。


例えば、render_component を使っているページにリクエストを飛ばすと、リクエストハンドラのオリジナルController => Component として定義された Controller x (render_componennt の回数) の形で、1リクエストが1+Nリクエスト分にふくれあがりユーザーは待たされます。

で。

1 リクエストが 1 + N リクエストになるんだったら、最初から 1+NリクエストとばすようにJavaScriptを書けばいいじゃない!と思って、コンポーネントとして定義されたコントローラーもURIとして見えるようにしています。

具体的には、Railsのルーティング規則で、特定のパスに対してリクエストが発生したときに、components 以下のコントローラーに直接つなげるようにする規則を追加しています*1

  map.connect 'components/:controller/:action/:id', :requirements => {:controller => /[a-z_]+\/[a-z_]+/i }

例えばデモサイトのトップページは3つのウィジェットからなりますが、ウィジェットコンポーネント内で定義されたコントローラーなので、1+3のリクエストがブラウザからサーバーにとばされます。Firebugで確認できますが、Ajaxリクエストが3つ必ず飛びます。

ところで、コンポーネントの処理のオーバーヘッドをAjaxによる非同期リクエストに変えただけで、1+N個のリクエストが処理されることには変わらないので、消費するコストという点では問題は改善されていません。ただし、Railsが持っている各種キャッシュ機能がそのまま使えるので、Read が多いウィジェット系では多くがキャッシュで返せると思います。そして、render_component をしたければ、Net::HTTP.start(localhost,3000)すればいいのです(普通は必要ないですし、renderよりコストは高いです)。

ただ、C10K問題を持っていることは確実です。1ページリクエストされたはずが、1+N個の非同期HTTPリクエストになってしまいます。そこで、、、

WebJourneyで作る必要のないコンポーネントGoogle Gadget におこうよ!

というのを推奨しています。iGoogle などで使用可能な Google Gadget の正体は 単なるiframeでGoogleのガジェットサーバーへのリンクを持っているものです。WebJourney は Google Gadget をそのまま配置するGoogle コンポーネントを使うことができます*2

これを使えば、1 + N - (google よろしく) 個のリクエストになるので、ページのすべてがGoogle Gadget だった場合は、なんの問題もありません。WebJourneyは単にページという枠を提供するだけです。

じゃ、WebJourneyで作る必要のあるコンポーネントは何?っていう話ですが、それはGoogle Gadgetとして実装しにくいものです。例えば自サイト内にあるDBを利用するものであるとか、私みたいに、自分のパーソナルなブログはGoogleなんかにおきたくない、というものであったり*3

最後に、コンポーネント間の連携に関して云えば、おそらく連係はJavaScriptでやるのがもっともスマートに見えます*4
具体的には、JavaScript側で、ウィジェットオブジェクトのJavaScriptインスタンスをもっていて、Pageオブジェクト(これもJavaScript版オブジェクト)を経由して、他のウィジェットを参照できます。

GoogleMap用ウィジェットは実装していませんが、こういうことができるかもしれません。

Page.getWidgetInstancesByName("google_map").each(function(widget){
   widget.setPoint(name, {x: ..., y: ...))
});

この点JavaScriptはかなり柔軟なので、独自実装で完全なものにしてもいいのですが、自分で使い道が思い当たらないことと、HTML5 のCross Document Messaging を各ブラウザが実装すれば自分はがんばる必要はないので、この当たりの機能は保留していて(確かver 0.4 => 0.5 で削除した記憶が)、少し妄想が入っています。

*1:実際には、components/{component_name}/_config/routes.rb でコンポーネントURIのパス空間内に独自ルーティングを切れるような仕組みも持っていて、RESTful なコンポーネントでも対応できるようにしています。そして components 以下に配置するコントローラーに対してレイアウトの機能であるとか、ユーザー認証・認可などのサイト全体で共有可能なデータを初期セットするComponentControllerという基底クラスを提供しています。あと、パッケージングの機能とデプロイの機能も...

*2:ちなみに、商用のポータル製品でGG対応したよ!というのには正直噴きました。いや、それでお金をとるとかありえんだろ、と思ってwww

*3:なのでWebJourneyのすべてのバージョンにはBlogコンポーネントがくっついていますw

*4:サーバーサイドで連係したければ(security reason?)、Net::HTTP使うか、無理矢理他のコンポーネントが使うDBをのぞきにいけば十分かな、というのもあります