イントロダクション

それを避けて通ることなどできません... StrutsはJavaアプリケーションフレームワークにおける800ポンドのゴリラです。 それはJavaアプリケーションフレームワークの世界に古くから存在し、幅広い適用範囲と数多くの採用を得ました。 そして、とても多くのアプリケーションがStrutsで書かれています。

Strutsは アクションベース のフレームワークであり、Servlet API 上の薄いラッパーとして動作します。 これはある程度は良いのですが、その世界観が制限されたものであることを示す例として常にTapestryが存在していました。

Tapestryは、特に Tapestry 5 では、Strutsとくらべてやや急進的なアプローチをとります。 あなたはそれに注目せずにはいられないことがわかるでしょうが... しかし、それには全く異なるWebアプリケーションの構築方法にあなたの考え方を合わせることも必要となります。

Struts: アクションとビュー

MVCデザインパターンによる問題の分離に対して、TapestryとStrutsは全く異なる角度からアプローチしています。

Strutsは入ってきたURLを アクション に対応付けします。アクションはActionクラスから派生したクラスのオブジェクトです。

アクションは何をするのでしょうか? 直接、またはアクションに関連したActionFormオブジェクト経由でリクエストのクエリーパラメータを読み出します。 おそらくデータベースとの間で情報の読み書きを行うでしょう。 また、ビュー のレンダリングに必要な情報をリクエストに格納します。 最後に、ビューの名前を返します。

Strutsフレームワークはそれを受け取り、そのビューの名前を使って(大抵JSPで書かれている)テンプレートを選択し、 ユーザへのレスポンスとしてレンダリングします。

Strutsはこのすべてをどのようにして見つけるのだろう? XMLファイルでたくさんの設定をします。 そこには、コントローラクラスの定義、ビューの名前とJSPの関連付け、 ビューとアクションとActionFormクラスの関連付けの詳細がどのページにも長々と続きます。

ステートレスの問題

Struts(と類似のフレームワーク)のどこがいけないのでしょう? 最初の問題は 状態 の管理に関することです。

Beginning Object Oriented Programming 101[TODO:書籍の名前か何か?]」を覚えていますか? 一般的に、オブジェクトは振る舞いと 状態 をカプセル化したものとして定義されます。

Strutsのアクションは内部状態を持っていません。持つことができないのです -- Strutsのアクションはシングルトンであり、サーブレットコンテナ内で動作している多数のスレッドで共有されています。 どんな内部状態も、つまり どんなインスタンス変数も、 他のユーザーのリクエストを処理している別のスレッドによってすぐに上書きされてしまいます。

この問題に対するStrutsのアプローチは、 HttpServletRequest(ビューだけが必要とする場合)または HttpSession(次のリクエストまで永続化しなければならない場合)の中に状態を外部化するというものでした。

ここで私たちは、振る舞いがひとつだけで内部状態を持たないオブジェクトという 不自由な形のオブジェクト指向プログラミングを背負うことになったのです。

Tapestryはこれらの問題の両方に対処します。Tapestryはコンポーネントに内部状態を持つことができ、 ひとつだけでなく複数の振る舞いを持つことが可能です。

ビュー: 不自然な仕切り

アクションフレームワークは振る舞いのロジック(Struts Actionクラス内のコード)とビューのロジック(典型的にはJSP)を分離します。

不自然な仕切り とは、テンプレートとコントローラ(Struts Action)の間の連携が不十分であることを指します。 HttpServletRequest と HttpSession が唯一の通信手段なのです。

この設計はビューを選択するという目的のためです。 アクションは複数のビューの中からひとつを選択するので、アクションとビューは疎結合である必要があります。 HttpServletRequestとその属性がこの疎結合を形作っています。

これは、ビューがレスポンスをレンダリングする際に必要となるかもしれないありとあらゆるデータを HttpServletRequest に詰め込む責任をアクションに課します。他の代替手段は全く無いのですよ?

それに加えて、HttpServletRequestやHttpSession内に格納される全てのデータの名前と型について、アクションとビューの間で一致させるという問題があります。 異なるデータの断片を格納したり異なる名前を使ったりといったコントローラのちょっとした変更が、ビューへ波及することがあります。

これは有意義なことでしょうか? (リンクのクリックやフォームのサブミットのような)アクションは何らかの成功または失敗という結果になります。 また、おそらくクライアントの種類に基づいた(PC向けにはHTML、携帯端末にはWMLといった)出力を行うことでしょう。

おそらく理論上はそうでしょうが、しかし 実際問題としては そうではありません。 全く別のビューとしてエラーが示されることは滅多に無く、同じビュー内に追加して示されるのが普通です。 単一のアプリケーションが異なる種類のクライアントに異なるマークアップを提供するということに関しても...それはただの伝説です。 それは出力だけではなく、アプリケーション開発のあらゆる側面に関係します。 それは何をどこに表示するというだけでなく、まさに機能とユースケースに関するものであり、それはあるデバイスと別のデバイスではとても異なっています。 そのような様々なクライアントをサポートする単一のアプリケーションを後ろに下がって見てみれば、 デバイス毎のアプリケーションがそれほど一貫性が無くパッケージされた、マルチアプリケーションであることに気づくでしょう。

コンポーネント: それは出力するだけではない

アプリケーションに共通の 固定具 があるとき、何が起こりますか? ビューの断片がアプリケーション内の多くの場所で使用されます。 これはどのページでも使用するメニューシステムのような機能の大きなかたまりから、フォーマットされた日付を出力する "(名前もない)何とか部品" のような小さな機能のものまで含みます。

JSPはこれに対してふたつのアプローチを提供します: 多くのJSPの間でJSP断片を再利用するために、JSPインクルードを使うことができます。 もうひとつは、JSPタグを定義し使用することです。JSPタグはコードにより出力を生成する方法を提供し、 またHttpServletRequestからJSPタグの実装へ情報を渡すメカニズムを提供します。 ああ、悲しいかな! 現実の世界ではとても多くのアクションが、慣習的に "success" と名付けられた ひとつの ビューだけを持っています。 だって、何かエラーが起きたときでも、全ての前後関係を失いたくはないでしょう ... ユーザーが不適当な情報をフォームに入力したとしても、エラーメッセージとともにフォームを再表示したいでしょう。 ビューはリクエストによって決定される状態に 適合 しなければならないのです。

しかし、"固定具" がそれ自身の振る舞いを持っていたら、それ自身の状態を持っていたら、どうでしょう?

例えば、ユーザーがログインするまで全てのページに表示されるログインフォームが固定具としてあるかもしれません。 そのフォームはログインアクションを指すURLを持っているでしょう ... しかし、ログイン後どのように元のビューに戻るのでしょうか? 似たようなケースとして、ページングをサポートした表形式のデータを表示するコンポーネントがあります。 そのようなコンポーネントにはナビゲーションとソートのリンクがあるでしょう。 それらリンクに対するアクションは、表示する項目一式を切り替えた後に、どのように元のビューに戻るのでしょう? ひとつのビューにそのようなコンポーネントが複数あったら?

あなたは、多くの構成と定型的なコードを書くことに終止することでしょう。 アクションを実行した後に表示する正しいビューをどのように得るのかを適切にアクションに伝えるために、 ますます多くのJSPアトリビュートを定義するか、他のトリックが必要となるでしょう。

あなたのそばには真のWebコンポーネントは無いという制限に出くわすでしょう。

Tapestryは、個々のコンポーネントがそれ自身のインタラクションを持ち、シームレスにページに融合しています。 それは複数のページにわたって同じコンポーネントが使用される場合でさえも。 Tapestryは自身がアプリケーションのビュー、つまりコンポーネントオブジェクトモデルを持っているので、 全ての物事を整理された状態に保つことができるのです。

Tapestry: The Full Cycle Solution [TODO:うまく訳せず...]

Tapestryが提供するのは 構造 です。適切に定義された、固定された構造のページとページ内のコンポーネントです。 この構造は、状態管理、出力レンダリング、リクエストディスパッチを含むTapestry内のあらゆる面において利用されています。

あたなは、URLについて何も気にする必要はありません。Tapestryに入ってくるリクエストは、 ページ名とそのページ内のコンポーネントIDを、他の情報とともにURL中にエンコードしています。 あなたのコードでURLの分解をする必要はなく、イベントハンドラメソッドを作成すれば、 ユーザーがリンクをクリックしたり、フォームをサブミットしたり、あるいはAjaxを使った何らかの操作したことを知ることができます。 全ての情報をURLの中に組み立て、次のリクエストでその情報を取り出すために必要な構造をTapestryは備えています。

本当に、Tapestryはオブジェクト指向です: シングルトンとマルチスレッドの問題を心配する必要はありません。 他のどんな普通のJavaオブジェクトとも同じように、ページとコンポーネントはインスタンス変数を持ちます。 Tapestryは、あなたのアプリケーションを真のオブジェクト指向を用いて書かせてくれます。

状態管理について: フィールド値のセッションへの永続化はTapestryが管理します。 セッションアトリビュートの一意な名前のニーモニックも、セッションからデータを取り出したりセッションに格納したりするコードを書く必要もありません。 Tapestryがやってくれます。

そのうえ、Tapestryではページとコンポーネントは同じものなのです。それらは 首尾一貫 しています。 ページはテンプレートを持っています。コンポーネントもそうです。ページは一時的なフィールドと永続フィールドを持っています。コンポーネントもそうです。 ページはイベントハンドラメソッドを持っています。コンポーネントもそうです。

ひとつのページに複数のビュー、という概念をTapestryは持っていません。 しかし、あるページを処理中に別の ページ へ「ハンドオフ」することがJavaコード内で簡単にできるので、 ユーザーからのアクションに対するレスポンスを別の ページ が提供することができます。

移行する

既存のアプリケーションをTapestry 5向けに変換する魔法のツールがあると期待しないこと。 Strutsや他のアクション指向のフレームワークの世界とTapestryの世界はあまりにも遠く離れています。

Tapestryではユーザーと同じ言葉でアプリケーションを考え始めることができます... 互いに結びついたページの集まりとして。

URLを生成したりそれらをシングルトンのクラスにマップしたりはしません; コンポーネント をページに配置し、 そのコンポーネントが起動された時に実行するイベントハンドリングメソッドをクラスに追加します。 たくさんのコンポーネント? 問題無い。単にイベントハンドラメソッドを追加するだけだ。

苦痛を忘れ去る

変更があるたびに、いつもアプリケーションをリビルドしてデプロイしなおすのに疲れていませんか? Tapestryにはライブクラスリロードという特徴があります。 コードやテンプレートを変更するとTapestryはその変更をすぐに反映します ... 待つことはありません! これまでのような制約無しでアプリケーションの構築がどれだけ素早く簡単にできるかをちょっと考えてみてください; スクリプト言語の軽快さとJavaの全てのパワーを。