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をのぞきにいけば十分かな、というのもあります