Transitive Path Strategyはやはり見通しが良い

今日はあるアプリを試しにT2で作ってみた。やりたいことは割とサクサクできる感じ。もちろん完成度はまだまだだが、そんなのは強力なコミッタ達と将来使ってくださるであろう大勢のユーザによってあっという間に解消されるだろう。今はサンプルを作ってみて雰囲気を感じて、しっくりこないところやナイスなアイデアをレポートし、ときおり見つかる不具合をIssueとして上げるフェーズ。少しずつ、少しずつ、ね。

ところで今回のアプリは当初Ymirで作ろうと思っていたので、Ymirで採用している「Transitive Path Strategy」に沿って作成してみたところ、すこぶる見通しが良い。

Transitive Path Strategyは今考えた造語で、Ymirの思想の一部にもなっている「URLと(HTML)リソースの物理配置を対応付ける戦略」である。具体的には、例えば「http://xx.yy/zzz/index.html」にアクセスされた場合にファイルシステムの「〜/zzz/index.html」を返すようなやり方である。

この戦略はHTTPというものが世に出てきた時には静的HTTPサーバで当たり前のように取られており、リソースにどのようなURLが割り当てられ、どのようにインターネット上で公開されるかがとても分かりやすかったのだが、CGIに代表されるリソースの動的生成技術が登場してから徐々にこの戦略からの乖離がはじまり、現在ではURLはリアルなリソースのファイルパスではなくバーチャルなパスを表すケースが多くなってきてしまっている。

URLが指すリソースが真に物理的な実体を持たないのであればURLがバーチャルであってもさほど困らないのだが、実際はリソースの元となるテンプレートが物理的に存在し、それを元に最終的なリソースが生成されるケースが多い。JavaではJSPがその代表的なものだ。

リソース生成のためのテンプレートが存在する場合、テンプレートの物理配置とURLを対応付ければ分かりやすいのだが、Javaの世界ではそうならないことが多かった。例えば「http://xx.yy/app/product/list.do」というURLに対応するJSPは、appアプリケーションが配備されたルートディレクトリ相対で「/WEB-INF/jsp/product/list.jsp」、ひどい場合は「/WEB-INF/jsp/productList.jsp」という位置に配置されていることが多かった。

これはひとえに「テンプレートをレンダリングするまでになんらかのロジックの処理を挟みたいから」である。処理を挟まなくてよいのであれば、単に「/product/list.jsp」という位置に配置し、「http://xx.yy/app/product/list.jsp」というURLでアクセスすれば良い。また、PHPのようにテンプレート自体にロジックを書ける言語の場合は処理を「挟む」必要はないため、同様に「/product/list.php」という位置に配置し、「http://xx.yy/app/product/list.php」というURLでアクセスすれば良い。これは分かりやすい。

Javaではテンプレートと処理を分離することが開発効率やメンテナンスコスト的に有利であるという考えからPHPのようにはしないことが多い。URLとオブジェクトのマッピングを保持しておき、URLにリクエストが来たら対応するオブジェクトのメソッドを呼び出し、その後テンプレートエンジンに処理を委譲する。

ところが2.2までのServletAPI仕様では1つのURLに複数の処理エンジンをマッピングして動作させることができなかった。これはすなわち、ロジックの処理が終わった後に同じパスのままテンプレートエンジンに処理を委譲することができないことを意味している。そのため、例えば上記の例で「*.do」というURLにロジック処理用のサーブレットを対応付けてしまうと、「/WEB-INF/jsp/product/list.jsp」のような、オリジナルと異なったパスに処理を委譲することでJSPエンジンをキックせざるを得なかった。

マッピングが拡張子ベースなのであれば、物理配置を変えずとも同じ場所に同じ名前で拡張子違いのテンプレートを置いておけばまだましなのではないか?」という考えもある。すなわち上の例であればJSPを「/WEB-INF/jsp/product/list.jsp」ではなく「/product/list.jsp」に配置する、という考えである。なるほど、確かにこれの方がURLとテンプレートの物理配置の差が少なく見通しが良い。ただしこれはこれで問題がある。

問題とは、「http://xx.yy/product/list.jsp」というURLへアクセスすることで、ロジックを飛ばしてJSPにアクセスできてしまう、ということである。事前に呼ばれているべきロジックが呼ばれていない状態でJSPレンダリングされると、画面が崩れる、Exceptionがスローされてエラー画面が表示される、といった困った事態が生じる。これらはまだ目をつぶることができるとしても、ApacheなどのHTTPサーバとサーブレットコンテナを連携しているケースで設定が不適切であれば、JSPそのものがWebブラウザに表示されてしまうこともあるだろう。これはセキュリティ的、知的財産権的にNGであることは少なくないだろう。これらの現象を避けるためには、直接は「*.jsp」にはアクセスできないような設定をアプリケーションのweb.xml等に追加する必要がある。ああ面倒だ。単にURLと同じようにテンプレートを配置したいだけなのに!

Javaでのこの悲惨な状況の救世主となったのが、ServletAPI2.3から導入された「サーブレットフィルタ」である。これはサーブレットと異なり、単一のURLに複数の処理エンジンをマッピングできるのである。これで全ての問題は解決である。すなわち、リクエストを受け付けた時、(1)最初にロジックの処理を行ない、次に(2)同じパスのままテンプレートエンジンに処理を委譲してやる、のである。以上(シャレではない)。これだけである。こうすることでテンプレートに直接アクセスされる危険性もなくURLとテンプレートの物理配置を合わせる事ができる。この結果、テンプレートの見通しが良くなるだけでなく、テンプレート中で相対パスで他リソースを参照していても問題なく表示される。うーん分かりやすい。

ロジックの処理後にテンプレートエンジンに処理を委譲するコードも(以下仮想的なコード)、

return Forward.to("/WEB-INF/product/list.jsp"); // ←JSPエンジンにこのパスでフォワード

の代わりに

return PassThrough.pass(); // ← フィルタチェインの次のフィルタに処理を委譲。実際の処理はサーブレットコンテナにお任せ!

で済んでしまう。すっきり!

ただし、Webフレームワークを使う場合はもちろんWebフレームワークがサーブレットフィルタとして動作する必要がある。そしてこの思想で作られたのがYmirである。…ってYmirの宣伝ぽくなってきた(笑)。

というわけで長くなってしまったが、Transitive Path Strategyはオススメである。ただしこれに基づいてアプリを作るにはWebフレームワークが「サーブレットフィルタとしてロジックを処理すること」という条件を満たしている必要がある。というわけで、Struts1.xよ、残念。そしてようこそ、Ymir!もちろんT2もフィルタベースで、自然にTransitive Path Strategyが適用できるようになっている。

Transitive Path Strategyも実践できる、「フィルタ」ベースのWebフレームワークを使ったことがない方は試しに使ってみてはいかがだろうか?