2010年2月13日土曜日

なぜ Lift に違和感を覚えるか

最近の私の仕事は Ruby on Rails 案件ばかりだ。そうなる以前、流行しつつある Rails を横目に見ながら Java 案件に取り組んでいた頃は、Java の融通の利かない言語仕様に苛立ち、一刻も早くJava から Ruby へ移行したいと思っていた。しかし、実際に Ruby で開発を行ってみると、そこには譲れないトレードオフがあった。Eclipse 上の Java コードは、死んだ文字列ではなく、呼びかければ応えてくれるオブジェクトだったが、Aptana RadRails 上の Ruby コードは、それとは程遠い代物だった。補完はまともに働かず、依存先へのナビゲーションや依存元の検索はインテリジェントでなく、リファクタリング支援は貧弱。これは、特定ツールの未成熟の問題というよりも、動的型付け言語のアイデンティティに関わる問題だろう。そして、Rails アプリケーションの実行速度は一般的に遅い。View のキャッシュで強引に解決できるかはドメイン次第だ。Ruby 1.9 対応や Merb 統合などの成果が意図通りに出るのかはよくわからない。

Java 案件時代に私が漠然と必要としていたのは、おそらく次のような特性を持つ言語だったはずだ。

  • 型推論を備えた静的型付け
  • 式言語が不要になる程度の表現力 (演算子的メソッドやプロパティ構文や名前付きパラメータなど)
  • ビルトインの動的プログラミング手段 (Java のリフレクション や Dynamic Proxy や バイトコードエンハンスメントなどよりも簡単に使えるもの)
  • クロージャ
  • Java と同等のパフォーマンス
  • Unix 系 OS でのまともな動作 (C# は残念ながらここで落選 - Mono ?)

そして、(今年予定の JDK7 でも Generics 限定型推論の導入とシンプルなクロージャの導入止まりな Java を差し置いて) 既に Scala がそこにある。Scala には、変態的な考え抜かれた言語仕様に基き上記のほとんどの特性が備わっているだけでなく、関数型パラダイムや Actor や Java 統合などの嬉しいおまけもついてくる。さらに、Scala には既に Rails 相当の Web フレームワーク、Lift がある。

だが、Lift の設計は、ざっと見た限りでは、大きな違和感を覚えさせるものだ。Rails は、いわば過去の MVC Web アプリケーションフレームワークのラディカルな書き直しであり、Java から Ruby へ (より詳細に言えば、Java の「Web アプリケーションフレームワーク + DI コンテナ + OR マッパー」の 3 点セットから、フルスタックの Rails へ) の移行は、少なくともアーキテクチャの理解の面では大きな困難を伴うものではなかった。Lift の場合は大きく事情が違う。

以下いくつか違和感のポイントを挙げてみよう。

POSO (Plain Old Scala Object) でない Domain Object
Domain Object の属性が object なのは不自然だ。クエリのタイプセーフ性という (唯一の ?) メリットは、払う代償と釣り合っているのだろうか。タイプセーフ性だけが目的であれば、メタデータクラスの自動生成などの別の解決策もあったのではないか。現状では Domain Object のライフサイクルはフレームワークに完全に握られ、アプリケーションスコープのロングキャッシュやセッションへの退避などはたぶん難しい。さらに、永続化部分を Lift ORM 以外のものに差し替えようとすると、Presentation 層のコードを大幅に書き換える必要がありそうだ。
Domain Object と Presentation 層の密結合
Lift の Domain Object には HTML フォーム出力機能までが癒着しているが、ひとつの Domain Object が複数の画面 (例えば、一般ユーザ向け画面と管理者向け画面や、PC サイト向け画面と携帯サイト向け画面) で違う View を持つことは普通にありうるし、また、HTML の View を伴わない RESTful API やバッチアプリケーションでも同じ Domain Object を使うはずだ。Domain Object が層をまたがっていること自体は構わない (Rails はそうして生産性を上げたし、それまで Java で層間分離と DTO の介在必須を主張していた人たちは Rails を見てあっさり宗旨替えしてしまった) が、View 固有の事情は、Scala であれば後付けの trait で mix-in できたりしないものだろうか。
View First Design それ自体
これまで Controller First なフレームワークに基づく開発で蓄積してきた設計ノウハウの一定部分が無駄になってしまう (そもそも、これまで Controller First であることの不都合を感じていない)。また、名詞を中心に HTTP メソッドに対応する動詞がぶら下がる RESTful なアーキテクチャでは、Rails のような Controller を中心にメソッドがぶら下がる Controller First のほうが自然な設計になるのではないか。非同期ページ要素更新が主な構成であれば View First の利点もあるかも知れないが。
リファクタリング耐性の低い Template View
Scala の表現力をもってすれば、Template View の式言語のポジションにそのまま Scala を置いて、Template View にリファクタリング耐性を持たせることができるはずだ。だが、Lift の取った選択肢は独自タグ (Snippet のメソッドコールに対応する) だった。テンプレートが一応 XHTML 的に valid になるというメリットはあるが、Tapestry や Wicket の Plain Old HTML Document バインディングほどは徹底していない。
責務の不明確な大クラス
とりあえず net.liftweb.http.Snet.liftweb.http.LiftRules を参照。例えば WebWork2 (後の Struts2) で慎重な Servlet API の隠蔽が行われていたのとは対照的だ。
ノイズの多い API
Domain Object の getSingleton は Convention over Configuration で解決すべきだ。RequestVar(Full("xx")) は冗長だ。Schemifier.schemify のパラメータには適切なデフォルト値があるべきだ。…など多数。一言で言うと、DSL 的でない。
独特すぎる命名
Rails の命名は、PofEAA に明示されているような慣習にある程度沿ったものだったが、Lift のそれはよくわからない。コアなクラスからして、上述の net.liftweb.http.S や、net.liftweb.mapper.Mapper (Data Mapper ではなく Domain Object) など、独特だ。さらに、ユーザの作成するクラスに関しては Rails の *Controller *Helper のような類型別の命名規約がないようだ (例えば、チュートリアルの ToDo モデルに関する Snippet の名前はなんと TD だ)。

Lift の生産性がある特定の分野にフォーカスして高いものであることに関してはその通りなのかも知れないが、幅広い分野に適用可能なスケーラブルな設計であるとは思えないし、少なくとも、初期学習コストを多く伴うハードランディングなソリューションであることは確かなようだ。Lift は、Scala による Web アプリケーションフレームワークの必然的な収束点だろうか ? おそらく違うと思う。よりソフトランディングな競合フレームワークが出現することを期待したい。