|
ニュース検索
ピックアップ
今週のIT求人情報
|
SeamでJavaプロジェクト開発を大幅に効率化するはじめに先日、ある友人が電話をかけてきて、今回のAtlanta JUGミーティングはJBossの新製品「Seam」の情報を得ようとする人々で大変な盛況だったと語りました。私はコンピュータの前に座ってSeamのことを20分ほど調べ、とても好印象を持ちました。SeamはJava EE 5(J2EE 3)の軽量な標準規格をベースにしており、新しいエンティティBean仕様、JSF、アノテーション、インターセプタ、セッションBeanといった技術を使用しています。Springと同様に制御の反転(Inversion of Control)を使用しますが、Springとは異なり、ステートフルなオブジェクトの注入を許可します。エンタープライズJava開発者が7年間もコツコツとやってきたデータ移動とフレームワーク/API操作の作業の多くがSeamによって解消されるのです。 ユースケースとユーザーストーリーは対話的(conversational)なやり方で要件を記録しますが、私の知る限り、Seamは対話的なやり方でコーディング作業を助ける最初の製品です。Seamを利用すると、アプリケーションのページレベルおよびBPMレベルのやりとりが、ファーストクラスのエンティティになり得ます。ユースケースとユーザーストーリーが実際にコードのモデルになるのです。 私は、もし自分の開発チームでSeamを使用していたらこの3年半にどれだけ多くのコードを節約できただろうかと考えてみて、Seamの有効性を実感しました。おそらく40〜60%のコードが不要になったと思われます。 その後、実際にSeamを試してみて、コーディングが減って開発が楽になるという話が本当なのだと確認できました。そのことを、本稿で証明したいと思います。 編集者注
Mark SmithはValtech Technologiesの取締役です。同社はSeamの開発元であるJBossとコンサルティングに関して協力関係を結んでいます。
Seamの概要Seamのメリットはいろいろありますが、私の考える重要な特徴は次の3つです。
SeamはJava EE 5仕様の概念をさらに推進し、軽量な開発とプログラマにとっての使いやすさを念頭に置いて設計されています。SeamはJSFとEJBのための強力な統合フレームワークを備えており、普通ならこれらの製品を統合するために使われるグルー(接着剤)コードが不要になります。開発者はオブジェクトからフォームやJSPへのデータ移動に時間を費やす必要がなく、ビジネスの価値を生み出すことに専念できます。 Seamによる統合レベルでのコスト削減の効果は、Seamの基盤であるHibernate/EJB 3エンティティBeanアプローチに最もよく現れています(表1を参照)。Seamにより、JDBC呼び出しからオブジェクトへのデータ移動およびその逆のデータ移動に必要なロジックが不要になります。また、クラスレベルの複雑な関係管理はSeamが担当してくれるので、開発者は、過去のJDBCアプリケーションでこれらの問題を処理するために必要とされたコードや外部キーの詳細を気にせずに済みます。Hibernateはこれらの概念をEJB 3.0仕様と共に導入しましたが、これらはJava標準として採用されています(このモデルの詳細については、「Simplify Java Object Persistence with Hibernate」を参照してください)。 表1 JDBC呼び出しとEJB 3/Hibernateアプローチでの行数の比較
Seamの宣言的状態モデル(図1を参照)では、状態を持つオブジェクトを管理するためのコンテキストを宣言できます。コンテキストになり得るものは、アプリケーション、セッション、対話(conversation)、ページ、イベント、BPMなどです。状態のコンテキストを宣言すると、Seamは必要な間はその状態を保持し、不要になると破棄します。状態管理によって、第2レベルのデータベースキャッシュ、ステートフルセッションBean、またはhttpセッションに関係する多数のバグとパフォーマンス上の問題が解決されます。また、APIの使用に関係するグルーコードも不要になります。 図1 Seamのモデル:Seamにより、JSFとJava EE 5仕様の他の部分の間のグルーコードの多くが不要になる。Seamは状態のコンテキスト管理を複雑なJava EE 5 Webアプリケーションに取り入れている(出典: JBOSS) ![]() 制御の反転(IoC)はSpringで広く知られるようになった概念であり、しばしばハリウッドのプロデューサー的な「Don’t call me, I’ll call you.(電話をかけてくるな。必要があれば僕からかけるから)」という言葉で表現されます。IoCの概念では、ある種の処理をアプリケーションコードから分離してフレームワークまたはコンテナに移し、ある種のAPIの管理をアプリケーションコードからコンテナに移します。依存性注入(dependency injection)はIoCの一種であり、指定されたオブジェクトの作成または管理をIoCコンテナに移します。以前の手法ではオブジェクトのフェッチとAPIを処理するコードを書かなければなりませんでしたが、依存性注入では、使用すべきオブジェクトが判断され、そのオブジェクトが作成または取得されて、アプリケーションコードに与えられます。これにより、アプリケーションコード内のオブジェクトが、そのオブジェクトを作成するAPIに依存しなくなります。 IoCと依存性注入という概念の利点は、制御のサブバージョン(subversion of control:SoC)とバイジェクション(bijection)の機能を備えたSeamにはっきりと表れています。IoCとSoCの違いは、オブジェクトを注入(inject=インジェクト)できるだけでなく、その逆方向の注入(out-ject=アウトジェクト)もできる点にあります。IoCコンテナは単一のインスタンスを作成または再使用し、そのリソースを必要とするオブジェクトにインジェクトします。アウトジェクトでは、注目すべき状態が含まれているオブジェクトを、後でインジェクトに使用するためにコンテナに入れることができます。IoCではリソースをオブジェクトに注入するだけですが、Seamでは両方向の注入が可能です。 Seamはインジェクションを遂行するために次のアノテーションを使用します。
インジェクトできるクラスを識別します。
オブジェクトをインジェクトします。
インジェクトされたオブジェクトで行われた変更を、それを確認する必要があるかもしれない他のオブジェクトに公開します(注:Springにはこれに相当するアノテーションはありません)。
Seamコンポーネント(
@Nameで識別)を宣言的状態管理フレームワークに入れます。Seamコンポーネントを状態管理フレームワークの対話レベルに入れます。
Seamでは、EJB 3.0のアノテーションとSeam独自のアノテーションを広範囲にわたって使用します。データベースマッピングとその他のAPIコンフィグレーション情報をアノテーションで指定することにより、サードパーティのフレームワークを使用する際の煩雑さが軽減されます。また、コードが別のXMLファイルに分離されないので、コードが理解しやすくなります。 SoCと宣言的状態モデルの組み合わせは非常に強力です。これにより、対話的なやり方でコーディングを行うことができます。複雑なページフローも簡単に作成することができ、特に、ネイティブな統合を実現しているJBossのjPDL製品を使用すればさらに効率が上がります(図2を参照)。古いプログラミングモデルではBPMタイプのツールをうまく活用できませんでしたが、JBossのjBPMはこのプログラミングモデルにたやすく適合します。 Seamによるページフローの管理Seamでページフローを管理する方法は2通りあります。1つはごく初歩的な方法で、ボタンを押すと、文字列が渡され、その文字列が次のページにマップされるという仕組みです。この文字列を返すルーチンに、複雑なページフローロジックが記述されています。次に示す例では、基本的なXMLファイルを使用しています。これは「ステートレスナビゲーションモデル」とも呼ばれます。 <navigation-rule> <navigation-case> <from-outcome>editCustomer</from-outcome> <to-view-id>/editCustomer.jsp</to-view-id> </navigation-case> <navigation-case> <from-outcome>selectCustomer</from-outcome> <to-view-id>/findCustomer.jsp</to-view-id> </navigation-case> <navigation-case> <from-outcome>findCustomer</from-outcome> <to-view-id>/findCustomer.jsp</to-view-id> </navigation-case> </navigation-rule> <navigation-rule> <from-view-id>/editCustomer.jsp</from-view-id> <navigation-case> <from-outcome>find</from-outcome> <to-view-id>/findCustomer.jsp</to-view-id> </navigation-case> </navigation-rule> Seamのもう1つのページフローモデルは「jPDL」と呼ばれます。jPDLは、プロセス定義言語を定義するXMLファイルです。jPDLには優れたグラフィカルインターフェイスが用意されており、複雑なページフローを扱う際に大いに役立ちます。 実験の開始Bruce Tateは『Beyond Java』という著書の中で、あるJ2EEアプリケーションを何回かの週末を費やして完成させた後に、同じアプリケーションをRuby on Railsで構築してみたら1回の週末で済んでしまったというエピソードを紹介しています。私も本稿で同様の試みをしたいと思います。私はこの3年半、COBOLからJ2EEにリファクタリングされた大手レンタカー会社のWebベースのレンタカーアプリケーションに取り組んできたのですが、これをSeamで書くとどうなるかを考えてみます。なお、この挑戦にあたっては、48〜72時間で終わらせるという目標時間を設定しました。 もちろん、このサンプルアプリケーションは、私のチームが長年取り組んできた実際の商用アプリケーションと完全に同じものではなく、スケールダウンしたバージョンです。とはいえ、使用している概念は、大抵の大規模な階層化J2EEアーキテクチャの基礎になっている一般的なものです。 このアーキテクチャでは、「5+1層」のパターンを使用しています。各層はそれぞれ一定の役割を担っています。階層化アーキテクチャでの依存関係の管理の仕方は、それぞれの層が直下の層にしか依存しないようにすることです。上方向の依存関係は有効ではありません。これは依存関係を管理し、大きな開発チームでうまく責任分担し、チーム全体で共通のやり方によって問題群を分析するための効果的な方法です。 各層はデータを転送する必要があり、そのデータはほとんど同じ形式になっています。そのため、私は「+1層」を作成し、すべての層のすべてのコンポーネントがそれに対して依存関係を持てるようにしました。この層には、実際のロジックが入っていないデータファイルが含まれています(図3を参照)。 図3 階層化アーキテクチャ:5+1階層アーキテクチャの各層はこのようになっている。コンポーネントはそれぞれの層に置かれ、依存関係はコンポーネント間で識別される ![]() 図3から分かるように、最上層はプレゼンテーション層で、これはStrutsに基づいています。第2層はアプリケーション調整層で、ここにはセキュリティシステムとの統合が含まれています。セッションEJBがこの層とのやりとりを管理します。この層にはアプリケーション固有のロジックも含まれており、ここでビジネスドメインエンティティ間のやりとりを管理します。第3層はビジネス層で、エンタープライズ内の主要なドメインエンティティと一群の再使用可能なロジックと機能が含まれています。第4層はシステムの残りの部分と外部リソースの間の通信を管理します。この層はDAOに基づいており、各外部リソースは通信を管理するために1つ以上のDAOを持っています。第5層は必要な外部リソースです。アプリケーションの中には外部リソースとしてデータベースを1つだけ持っているものもあれば、18個もの異なる外部リソースを使用するものもあります。このアーキテクチャに基づくアプリケーションで、複数のコンポーネントとDAOを再使用するアプリケーションはいくつもあります。 データベース層は、Reservation、Location、Customer、CarClassという4つのテーブルから成ります。このデータベーススキーマはSeamアプリケーションで使われます。 アプリケーションフローはユーザーがCustomerレコードをReservationレコードに関連付けるところから始まります。ユーザーは次にピックアップとドロップオフのLocationレコードおよび両方の日時をReservationレコードに関連付けます。CarClassレコードはReservationレコードに関連付けられます。カークラスレートと予約期間に基づいて見積もり料金が計算され、Reservationテーブルに書き込まれます。 完成した予約エントリには、顧客参照、カークラス参照、ピックアップ(借り出し)とドロップオフ(返却)の場所、ピックアップの日時、ドロップオフの日時、そして最後に見積もり料金が含まれることになります。 Seamに取り組む週末私はSeamを使った48〜72時間の実験に取り組むにあたり、Hibernateコードジェネレータを使ってコードベースの最初の部分を作成しようと決めました。このツールはJBoss IDE JEMS製品の一部であり、Seamのスケルトンアプリケーションを作成するオプションがあります。個々のデータベーステーブルに対して次のコンポーネントが生成されます。
例えばLocationテーブルの場合は、このコードジェネレータによって2つのJSFページ、4つのクラス、2つのインターフェイスが生成されました(図4を参照)。参考までに、それぞれの生成コンポーネントについて簡単に紹介しておきます。
図4 コードの生成:基本データベーステーブルであるLocationテーブルに対してコードジェネレータを実行した様子。2つのJSFページと、それらのJSFページをサポートするためのSeamコードが生成される。「editLocation.jsp」はLocationテーブルの行を作成または修正するためのWebページを提供する。「findLocation.jsp」はLocationテーブルの行を検索したり、それらの行をページングしたり、それらの行の1つを選択するためのWebページを提供する ![]() 生成されたコンポーネントをコンパイルしてJBossにデプロイすれば、Location、CarClass、Customer、Reservationの各テーブルの検索、ページング、追加、削除、更新が可能になります。ここまでの作業には1時間もかかりませんでした。 しかし、私は何もかも気に入りませんでした。例えばReservationテーブルでは、ピックアップとドロップオフの場所の主キー、カークラス、顧客を予約に関連付けるためには、これらの情報を入力しなければなりませんでした。また、Reservationオブジェクトでは、これらのクラスを参照するときにオブジェクトではなく整数を使用していました。私は、この関係をもっとうまく管理するコードを生成したいと考えました。このようなコードが実際に生成されている例を見たことがあったので、それが可能なことは分かっていました。そこで、データベースで外部キーを使うことにしました。 CarClassへの外部キーを作成し、コードを生成してみたところ、その結果は満足のいくものでした。この段階で、予約作成テーブルにアタッチされたボタンをクリックすると、「findCarClass.jsp」ページが呼び出されるようになりました。このページから 図5 車の選択:このスクリーンショットは、ユーザーが予約作成ページからカークラスを選択するところを示している。予約作成ページから「findCarClass.jsp」ページを呼び出し、[Find]ボタンをクリックして検索を行い、目的のカークラスの横の[Select]ボタンをクリックする ![]() この時点では、外部キーをあと3つ(Customerテーブルに対するものが1つ、ピックアップとドロップオフの場所に対するものが2つ)追加すれば、作業の95%は完了すると思っていました。しかし、ここで最初の障害にぶつかりました。新たに外部キーを追加した後、生成されたコードはコンパイルされなかったのです。 エラーをよく調べてみると、ピックアップとドロップオフの場所に対するLocationテーブルへの外部キーを作成したので、場所と予約の間のやりとりを管理するいくつかのオブジェクトでメソッドが重複していることが分かりました。余分なメソッドをコメントアウトすれば簡単に修正できるエラーのように見えましたが、生成されたコードのフローをたどっていくのは手間がかかりました。4時間かかってデバッグを終えた後、コードは正常にコンパイルされたので、その夜はそこで仕事を切り上げました。 次の朝、コードをデプロイしましたが、予約に場所を追加するたびに例外が発生することに気付きました。昼食時になる頃、つまり4時間ほどあれこれ調査したのちに、事態を改善しようとした策が逆に事態を悪化させたことが明らかになりました。 そこで現在のアプローチを断念し、何かもっとうまいやり方を試すことにしました。まず、Locationテーブルから外部キーの1つを削除し、コードを生成し、コンパイルを行ってデプロイしました。10分もかからずに、CarClassとCustomer、さらに1つのLocationを統合したサイトを構築することができました。まず1つの場所だけを使って試してみるので、jspに手作業で機能を追加するにあたり、この時点ではバックエンドコードを変更しないようにしました。 この作業の結果、JSFコードは次のようになりました。 <div class="rvgResults"> <h2><h:outputText value="#{msg.Reservation_PickUplocation}"/></h2> <h:outputText value="#{msg.No} #{msg.Reservation_location}" rendered="#{ ’’reservationEditor.instance.pickUpLocation’’ == null}"/> <h:dataTable var="parent" value="#{ ’’reservationEditor.instance.pickUpLocation’’}" rendered="#{ ’’reservationEditor.instance.pickUpLocation’’ != null}" rowClasses="rvgRowOne,rvgRowTwo"> <h:column> <h:column> <f:facet name="header"> <h:outputText value="#{msg.Location_street}"/></f:facet> <h:outputText value="#{parent.street}"/> </h:column> <h:column> . . . <h:column> <f:facet name="header"> <h:outputText value="#{msg.Location_closetime}"/></f:facet> <h:outputText value="#{parent.closetime}"/> </h:column> <h:column> <f:facet name="header"> <h:outputText value="#{msg.Action}"/></f:facet> <h:commandButton action="#{ ’’reservationEditor.pickUpLocation’’}" value="#{msg.View} #{msg.Location}"/> </h:column> </h:dataTable> <span class="rvgPage"> <h:commandButton type="submit" value="#{msg.Select} #{msg.Location}" action="#{reservationEditor. ’’selectPickUpLocation’’}" /> </span> </div> ピックアップの場所を表示するためにフロントエンドにフックを設けてあるので、ピックアップの場所を定義するためにReservationコンポーネントにメソッドを追加する必要がありました。「Reservation.java」を修正して、ピックアップとドロップオフの場所の変数が両方とも 「ReservationEditor.java」には、 ReservationEditor内の新しいメソッドはLocationSelectorインターフェイスとその静的インナークラスを使用します。ReservationとLocationの関係を統合するのに役立つインナークラスがあるので、そのインナークラスのコピーを2つ作成して名前を変更し、ピックアップとドロップオフの場所の選択に使用できるようにしました。さらに、インスタンスをインジェクトする際にこれらのクラスを識別できるように、アノテーション @Stateless @Name("reservationPickUpLocationSelector") @LocalBinding(jndiBinding = アノテーションの威力とSeamがそれらをどう使用しているかを実際に体験してみて、私はSeamを実に素晴らしい技術だと思うようになりました。 次に、「ReservationEditor.java」に追加したメソッドを修正して、先ほど作成した選択インナークラスでうまく機能するようにする必要がありました。 変更前
@Begin(join = true) public String selectPickUpLocation() { CONVERSATION.getContext().set("locationSelector", Component.getInstance("reservationLocationSelector", true)); return "selectLocation"; } @Begin(join = true) public String selectDropOffLocation() { CONVERSATION.getContext().set("locationSelector", Component.getInstance("reservationLocationSelector", true)); return "selectLocation"; } 変更後
@Begin(join = true) public String selectPickUpLocation() { CONVERSATION.getContext().set("locationSelector", Component.getInstance("reservationPickUpLocationSelector", true)); return "selectLocation"; } @Begin(join = true) public String selectDropOffLocation() { CONVERSATION.getContext().set("locationSelector", Component.getInstance("reservationDropOffLocationSelector", true)); return "selectLocation"; } このような単純な変更により、「findLocation.jsp」ページとReservationEditorでlocationSelectorのまったく異なるインスタンスを使用することになります。この異なるインスタンスは、「editReservation.jsp」ページでどのボタンが選択されたかに応じて対話状態コンテキストに入れられます。ReservationEditorまたはそのクライアントである「createReservation.jsp」ページがlocationSelectorを参照すると、常に適切なインスタンスが取得されます。Selectorはボタンラベルやページタイトルなどを管理して、jspページが再使用されたときに、どういう理由でどの場所が選択されたかを識別できるようにします。 まだ解決すべき問題が1つ残っていました。「createReservation.jsp」ページでどのボタンが選択されたかによって、「selectLocation.jsp」の画面タイトルを変更する必要があります。ピックアップ、ドロップオフ、一般のいずれの場所であるかに応じて変わるようにする1行が必要でした。私は簡単な道を選び、2つのファイルの間で1行だけを変更して、2つのクラスを新たに生成しました。さらに、この2つの新しいクラスを使うように予約エディタを修正しました。具体的には、「ReservationEditor.java」を次のように変更しました。 変更前
@In(value="locationEditor",create = true)
private transient LocationEditor locationEditor;
public String pickUpLocation() {
locationEditor.setNew(false);
locationEditor.setInstance(instance.getPickUpLocation());
locationEditor.setDoneOutcome("editReservation");
return "editLocation";
}
public String dropOffLocation() {
locationEditor.setNew(false);
locationEditor.setInstance(instance.getDropOffLocation());
locationEditor.setDoneOutcome("editReservation");
return "editLocation";
}
変更後
@In(value="pickUpLocationEditor",create = true)
public String pickUpLocation(LocationEditor locationEditor) {
locationEditor.setNew(false);
locationEditor.setInstance(instance.getPickUpLocation());
locationEditor.setDoneOutcome("editReservation");
return "editLocation";
}
@In(value="dropOffLocationEditor",create = true)
public String dropOffLocation(LocationEditor locationEditor) {
locationEditor.setNew(false);
locationEditor.setInstance(instance.getDropOffLocation());
locationEditor.setDoneOutcome("editReservation");
return "editLocation";
}
Springにはメソッドにインジェクトする機能がありますが、使用は推奨されていません。私はSeamのこの機能を使って、ロケーションエディタの特定のインスタンスをメソッドシグニチャにインジェクトすることができました。変更前のコードではクラス変数へのインジェクションが見られ、変更後のコードではメソッドシグニチャ上の変数へのインジェクションが見られます。 ここまでSeamの実験にかけた時間は16時間になります。これだけの時間で、4つのデータベーステーブルのCRUD、検索、ページングが可能なアプリケーションを生成することができました。また、 2つの実装アプローチの比較Seamの実験で節約されたコーディング量を把握するためには、アプリケーションの各部分を見比べていくのが良いでしょう。 例1元のアプリケーションではHttpSessionの状態の保守に多大な労力が投入されていました。HttpSessionのラッパーがあって、状態の追加とイベントとの関連付けを行っていました。イベントが発生すると、このラッパーがHttpSessionをクリーンアップして、そのレベルと下位レベルのイベントをすべて除去していました。Seamでは、こうしたものを書いたり保守したりする必要はまったくありません。 具体的なコードを見てみましょう。元のアプリケーションでは、 XDelegate delegate = getDelegate(request); … protected XDelegate getDelegate(HttpServletRequest request) { Object delegate = SessionManager.getAttribute(request, DELEGATE_KEY); if ((delegate == null) || (!(delegate instanceof XDelegate))) { delegate = new XDelegate(); SessionManager.setAttribute( request, DELEGATE_KEY, delegate, EventLevel.Screen); } return (XDelegate) delegate; } Seamでは、このコードは次のようになっています。 @In(value="xDelegate", create=true) @Out XDelegate delegate; … @Name("xDelegate") @Scope(PAGE) public class XDelegate implements AbstractDelegate{ … } 例2 元のアプリケーションは、60人以上の開発者から成るチームで取り組んでいましたが、統一的な体系がないためによく問題が発生しました。このアプリケーションでは、動作結果を1つの場所に記録するために 対話状態の管理 対話を開始するには 表2 サンプルアプリケーションのビジネスロジックを設計するために使われるシステム要件
これはシステムの出資者にとってビジネス価値のあるアイテムを作成するための一連のやりとりです。しかし、これをどうやってコードにマップするのでしょうか。元のアプリケーションでは、ステートフルセッションBeanのインターフェイスに相当な量のロジックがマップされていました。 public CustomerListTR retrieveCustomers() public ReservationTR addCustomer(ICustomerVO cust) public AllLocationsTR retrieveAllLocations() public ReservationTR assignPickUp(ILocationVO loc, Date dat) public ReservationTR assignDropOff(ILocationVO loc, Date dat) これらのメソッドの背後には、コンポーネントを取得したり、何かをするように要求したり、状態を管理したりするコードが大量に存在します。元のアプリケーションでは、外部システムとのやりとりの大部分で非同期メソッド呼び出しを使用していました。パフォーマンス上の理由から、ユーザー側が開始する何回かのユーザー/システム間のやりとりにわたって、非同期呼び出しの応答を保持する必要があったのです。私はその状態をhttpセッション、ステートフルセッションBean、およびエンティティBeanに格納することを試みましたが、どのソリューションも対話向きではありません。これらの方法では、オブジェクトを状態管理システムに入れ、不要になったらそこから取り除くためのコードを書く必要があります。そして、明示的に処理されない方法でユーザーが対話を終了した場合には、厄介なバグが発生する可能性があります。 Seamアプリケーションでは、オブジェクトが必要になったらインジェクトし、必要に応じて対話を開始したり終了したりします。そのため、対話状態に対応していない状態管理モデルで対話状態を管理するという余計な仕事に煩わされず、本来のビジネスロジックに専念できます。Seamにより、表2の要件によく似た形のコードを書くことができるのです。 @In(value="reservationFinder", required = false) private transient ReservationFinder reservationFinder; @Begin(join = true) @IfInvalid(outcome = Outcome.REDISPLAY) public String create() { @IfInvalid(outcome = Outcome.REDISPLAY) public String update() { @End(ifOutcome = "find") public String delete() { @End(ifOutcome = "find") public String done() { @In(create = true) private transient CustomerEditor customerEditor; public String customer() { @Begin(join = true) public String selectCustomer() { @In(create = true) private transient CarclassEditor carclassEditor; public String carclass() { @Begin(join = true) public String selectCarclass() { @In(value="pickUpLocationEditor",create = true) private transient LocationEditor pickUpLocationEditor; public String pickUpLocation() { @Begin(join = true) public String selectPickUpLocation() { @In(value="dropOffLocationEditor",create = true) private transient LocationEditor dropOffLocationEditor; public String dropOffLocation() { @Begin(join = true) public String selectDropOffLocation() { フロントエンド最後に、このアプリケーションのフロントエンドに注目して、Seamがどのような働きをしているかを確認しましょう。元のアプリケーションのAppCoord層(図3を参照)は、Struts、Swing、SOAP、JMSなど、何種類かのクライアントと通信します。この負担を軽減するために、現在の要求に関係するデータは値オブジェクト(VO)から転送オブジェクト(TO)に移されます。元のアプリケーションでは、VOへの修正を隠すためにTOを設け、VOをAppCoord層のクライアントから切り離すために抽象層を設けています。これにより、現在の操作を実行するために必要なデータだけを交換することになります。 TOは、メッセージ、問題、デコレーションインジケータを各フィールドに関連付けられるようにするために、StringとDateをはじめ、すべての基本型をラップしています。これらのラッパーにより、この情報をクラスレベルで関連付けることができます。この情報をReturnResultとVOからTOとラッパーに移すためのコードを作成するにはかなりの手間がかかります。 例えばStrutsなら、 Seamでは、こうしたデータ移動のためのコードがそっくり不要になります(表3を参照)。SeamはJSFへの拡張機能を提供するので、Strutsアクションの必要性もなくなります。Seamフレームワークの利点は、もし必要ならTO概念を追加できるところにあります。JMSベースやSOAPベースのメッセージ内のデータを共有するつもりなら、私はおそらくVOを使用しないでしょうが、SeamコンポーネントとJSFの統合の威力には注目すべきものがあります。 表3 StrutsとSeamとでの行数の比較
Seamを縫い合わせるSeamの好きなところの1つは、Java EE 5で開いたままになっている穴を埋めてくれるという点です。通常は開発者がコードを書くことでAPIとフレームワークを管理していますが、Seamではこの管理が自動的に行われるので、開発者はもっと大局的な問題に時間を使うことができます。しかも、特定のアーキテクチャを強いられることはありません。 このAPI管理だけでも大きなメリットですが、Seamのコンテキストによる状態管理は、要件リストによく似た形のコーディングを可能にします。これにより、アプリケーションサーバーでJava仕様がどのように実装されているかということに気を取られず、実際の機能に専念できるようになります。また、EJBの複雑さに頭を悩ませなくて済むという利点もあります。 とはいえ、今回作成したSeamアプリケーションにも不満な点がいくつかあります。生成されるコードで分離の問題をもっとうまく処理できる可能性があります。例えばEditorとFinderは、それぞれの担当処理をこなしているのはもちろんですが、これらのコンポーネントのメソッドの多くは、ページフローモデルで次に表示すべきページを決定するのに使われる文字列を返します。つまり、編集と検索だけでなく、ページフローとページングも行っているのです。 Selectorはリストからオブジェクトを選択しやすくするだけでなく、ボタンやページタイトルのラベルの決定も行います。私が最も気に入らないのは、LocationSelectorがReservationEditorにコールバックすることです。これは正しいオブジェクト関係についてのオブジェクト指向の基本原則に違反しているのではないでしょうか。個人的には、検索を行ってファインダに一覧表示するSQLの作成はバックエンドのコードで取り扱うべきであり、次に表示するページを決定するコードからは分離させた方がいいと考えています。今回のアプリケーションの 生成されたコードは こうした点を別にすれば、Seamは統合やビジネスロジックやフロントエンドを実現するために必要なコードを減らすことができます。今回取り上げたアプリケーションの 著者紹介Mark Smith(Mark Smith)
Valtech Technologies, Inc.(www.valtech.com)取締役。軍需産業に13年間従事したのち、コンサルティング業界に転身。現在はValtechの取締役として、100人以上の開発者と数百万行のコードを抱えるJ2EEの全プロジェクトを技術面で監督。
Markと彼のValtechチームはこれまで80人以上のCOBOL/ウォーターフォール型プログラマにJava/J2EEとAgileの方法論を指導して実績を上げている。また、Markは上級スタッフメンバらと共に、400人の開発者を擁するValtech社の戦略的指針を検討する立場にある。 |