J2EEのベストプラクティス・トップ10(+2)(前編)


Kyle Brown, Senior Technical Staff Member, IBM Software Services for WebSphere? Keys Botzum, Senior Consulting I/T Specialist, IBM Software Services for WebSphere? Ruth Willenborg, Senior Manager, IBM WebSphere? Performance 2004/7/23


主な内容

  • 必ずMVCを使うこと
  • すべてのレイヤにテストツールを用意し、ユニットテストを自動化すること
  • アプリケーション・サーバの仕様ではなく標準仕様に基づき開発すること
  • J2EEセキュリティの利用を当初から検討すること
  • 理解できる範囲でビルドすること

 J2EEのベストプラクティスについては、過去5年にわたり多数の文章が記されてきている。J2EEアプリケーションの開発ノウハウを解説した記事は数多く公表されており、出版された書籍もおそらく10を超えるだろう。J2EEに関する情報源はあまりにも多いため、互いに矛盾する方針を示していることもある。実際のところ、この“情報の迷路”こそが、J2EE導入における障害となっていたのである。

 筆者らは、この迷路でさまよえる読者に向けた簡単なガイドとして、J2EEにおける最も重要なベストプラクティスのトップ10リストを作成した。ただし、WebサービスもJ2EEの一部分として含めたところ、リストの項目は10を超えてしまった。そこでここでは、成長を続けるJ2EEに敬意を表し、トップ10の代わりに「トップ12リスト」とすることにしたい。

必ずMVCを使うこと

  • すべてのレイヤにテストツールを用意し、ユニットテストを自動化すること
  • アプリケーション・サーバの仕様ではなく標準仕様に基づき開発すること
  • J2EEセキュリティの利用を当初から検討すること
  • 理解できる範囲でビルドすること
  • EJBコンポーネントを利用するときは、Session Facadeを常に使うこと
  • ステートフルSession BeanではなくステートレスSession Beanを使うこと
  • CMT(Container Managed Transaction)を使うこと
  • プレゼンテーション手段としてはJSPを最優先に使うこと
  • HttpSession?にはビジネス・ロジックに必要な最小限のデータだけを保管すること
  • WebSphere?では、ダイナミック・キャッシング機能をオンにし、サーブレット・キャッシング・メカニズムを利用すること
  • O/Rマッピングの手段として、まずはCMP Entity Beanを利用し、プログラマーの生産性を高めること

 前編では、上記のベストプラクティスのうちベスト5までを解説しよう。

1. 必ずMVCを使うこと

 ビジネス・ロジック(JavaBeans?やEJBコンポーネント)とコントローラ(サーブレットやStrutsアクション)、プレゼンテーション(JSPやXML/XSLT)をそれぞれ明確に分離すること。こうしたレイヤ分けによって、さまざまな問題の発生を防ぐことができる。

 MVC(Model View Controller)モデルは、優れたJ2EEアプリケーションを設計するための基盤となるアーキテクチャだ。J2EEの導入を成功に導くうえでは、このMVCの利用に勝る方策はないといえるだろう。要するに、プログラムがこなすべき仕事を以下の3つに分割すればよいのである。

MVCモデルプログラムがこなすべき仕事
ビジネス・ロジック(モデル)通常のJavaオブジェクトもしくはEnterprise JavaBeans?によって実装される
ユーザー・インターフェイスのプレゼンテーション機能(ビュー)JSPとタグライブラリによって実装される。場合によってはXMLやXSLTも利用される
アプリケーションの画面フロー制御(コントローラ)Javaサーブレットもしくはそれに関連するクラス(Strutsアクションなど)によって実装される

 J2EEにおけるMVCの利用に関しては、優れた解説書がいくつか出版されている。とりわけ、Martin Fowler氏の「Patterns of Enterprise Application Architecture」(Addison-Wesley、 2002)、そしてKyle Brown氏の「Enterprise Java Programming with IBM WebSphere?, 2nd Edition」(Addison-Wesley 、2003)の2冊は、このトピックについて広く深く掘り下げており、一読をお勧めしたい。

 MVCモデルを無視してJ2EEアプリケーションの設計を進めると、いくつもの問題が生じることになる。その大半は、ビューに多くを詰め込みすぎてしまうことに起因するものだ。例えば、小規模なアプリケーションでは、JSPの中で画面フローを制御したり、タグライブラリを用いてデータベースにアクセスしたりするものも多い。しかしこの方法では、開発が進むに従い、JSPのメンテナンスやデバッグが次第に困難になってしまうのだ。

 これとは逆に、本来はビューが担当すべき仕事をモデルに持たせている場合もしばしばある。よくある例としては、ビューで実装されているXMLのパース処理をモデルに移動してしまう状況が挙げられる。しかし、モデルはビジネス・ロジックの実行に専念すべきであり、ビューに依存する特定のプレゼンテーション処理を担当すべきではない。

 また、モデルとビュー、コントローラを単に設けただけで、レイヤ分けが不十分な場合もある。例えば、サーブレットとJSP、EJBによりMVCを構成しているものの、ビジネス・ロジックの多くをサーブレットで実行していたり、フロー制御をJSPが担当していたりするケースが多い。こうした間違いを避けるためには、コード・レビューやリファクタリングを厳格に実施すべきだろう。ビジネス・ロジックはモデル、フロー制御はコントローラに集中させる。そしてビューは、モデル内容をHTMLやJavaScript?で表現する処理に専念させるのである。

2. すべてのレイヤにテストツールを用意し、ユニットテストを自動化すること

 GUIのテストだけで済ませてはならない。すべてのレイヤでテストを実施することで、デバッグやメンテナンスが大幅に簡素化される。

 ここ数年、オブジェクト指向開発の分野では、大規模な変革が進みつつある。それは、XP(eXtreme Programming)やSCRUMなど、「アジャイル」と呼ばれる小回りの利く方法論の普及である。これらの方法論のほぼすべてに共通する特徴は、自動化されたテストツールの利用を徹底させている点だ。これにより、開発者が回帰テストに費やす時間を減らして生産性が高まるほか、不十分な回帰テストによるバグの残留を防ぐことができる。例えば、「テストファースト開発」と呼ばれるプラクティスでは、プログラム・コードを書き始める前にユニットテストを記述することで、テストの自動化を一段と推し進めている。

 コードのテストを実施するには、単体でテスト可能な部分を切り出さなくてはならない。コード全体が大きな塊のような状態で、複数の機能が絡み合っていると、個々の機能の正しさをテストすることは困難である。これに対し、MVCモデルをベースとするJ2EEアプリケーションでは、全体が複数のコンポーネントに分割されるため、それらを個別にテストするのが比較的容易である。例えば、EJBのEntity BeanやSession Bean、JSPなどに対し、個別のテストを記述するのは難しくはない。また、こうしたJ2EEのテスト作業をサポートするためのフレームワークやツールもいくつか提供されている。その例としては、junit.orgが開発したオープンソースのツール「JUnit」をはじめ、Apacheコンソーシアムのプロジェクトである「Cactus」などがある。いずれも、J2EEコンポーネントのテストにおいて大変役立つものだ。

 アプリケーションを隅々までテストすることのメリットは、各方面で指摘されている事実だ。それでもなお、GUI(WebベースもしくはJavaアプリケーションによるユーザー・インターフェイス)のテストだけをもってシステム全体のテストに代えてしまうプロジェクトは後を絶たない。

 GUIのテストだけでは不十分な理由は数多くある。その1つは、システムのコード全体を網羅するのが困難な点だ。GUIを通じて触れることができるのは、システムの一側面にすぎない。その背後には、バックグラウンド・ジョブやスクリプトなど、テストすべきさまざまな部分が存在する。しかし、いずれもGUIを持たないことが多いのである。2つ目の理由は、非常にきめの粗いテストしか行えない点だ。システムの振る舞いについてマクロなレベルでしかテストできないので、問題が発見されても対象範囲が広過ぎてバグの特定が容易ではない。

 3つ目は、GUIがすべて整うプロジェクトの後半にならないとテストを実施できない点。潜在的なバグのいくつかは、かなり後になってから発見されることになる。そして4つ目は、GUIテストではツールによる自動化が難しい点だ。GUIに何らかの変更が加えられるたびに、その部分を再テストするための修正作業も必要となる。これでは、十分なテストは行えない。一方、自動化されたユニットテストが整っていれば、変更による影響をほかの部分が受けていないことをすぐに確認できる。

 最後の理由は、ユニットテストの自動化によって、自動ビルドと簡単に統合できる点だ。これにより、システムの定期的な再ビルドの際(通常は夜間)に、人手を介すことなく回帰テストを実施できるのである。

 EJBやWebサービスによる分散コンポーネント・ベースの開発では、各コンポーネントの個別テストが不可欠なことも付け加えておきたい。GUIを持たないコンポーネントに対しては、きめ細かなテストを記述するしか方法はない。また、こうした手順を初めから導入しておけば、アプリケーション機能の一部を分散コンポーネントやWebサービスとして提供することになった場合でも、開発手順の大幅な変更に頭を悩ませずに済む。

 要するに、ユニットテストの自動化を徹底することで、アプリケーションの不具合をより早くより簡単に発見できる。そして、システマティックなテストによって、アプリケーション全体の品質が向上するのである。

3. アプリケーション・サーバの仕様ではなく標準仕様に基づき開発すること

 標準仕様を頭にたたき込み、よほどの理由がない限りそこから逸脱しないこと。「できること」と「すべきこと」の違いを理解すべし。

 J2EEという名の柵をむやみに跳び越えれば、そこには大きな落とし穴が待っている。J2EEを“少しだけ改善”するための行為が、パフォーマンスやポータビリティ(ベンダ間やバージョン間の移行)にかかわる深刻な問題を後になって引き起こすのである。事実、Wayne Beaton氏の記事では、マイグレーション作業におけるベストプラクティスとして、このルールが真っ先に挙げられている。

 必要以上に複雑なアプローチを取ることが致命傷となる場合も少なくない。よくある例としては、アプリケーション・サーバに内蔵されたJ2EE準拠のセキュリティ(認証や承認)機能を使わず、JAASモジュールでそれを置き換えてしまうケースがある。しかし、J2EE仕様が定める認証のメカニズムに手を加える作業は、特に慎重を期す必要がある。さもなければ、深刻なセキュリティ・ホールやベンダ間互換性の問題を招いてしまう。よって基本的には、J2EEアプリケーションの設計は、サーブレット仕様やEJB仕様に定められた認証機能に沿う形で行うべきである。そして、機能拡張が必要な場合は、標準のAPI(getCallerPrincipal?メソッドなど)をできるだけ利用して実装する。この方法により、業務の要件に応じてベンダ製の強力なセキュリティ・インフラを利用したり、さらに複雑な認証ルールをサポートしたりできる。

 J2EEからの逸脱の例はほかにもある。J2EE仕様とは独立した永続化(データベース保存)機能の利用により、トランザクション管理の統合が困難になったケース。また、マルチスレッドやシングルトンなど、J2EEでの利用が制限されている機能に依存したケース。そして、JCAやJMS、Webサービスなどの標準技術を用いず、独自の手段でプログラム間通信を実装したケースなどである。これらのような設計では、他ベンダのJ2EEサーバへの移行時ばかりか単なるサーバのバージョン・アップ時にさえ、複雑なポータビリティの問題を引き起こしかねない。

 標準仕様からの逸脱は、標準仕様ではカバーできないことが明らかな場合にのみ限るべきだろう。例えば、EJB 2.1が登場するまでは、一定のスケジュールの下でビジネス・ロジックを定期実行する機能が標準化されていなかった。こうした場合は、ベンダが提供するソリューション(WebSphere?? Application Serverエンタープライズ版のScheduler機能など)を用いるか、サードパーティ製ツールを利用すべきである。これにより、いずれ標準仕様に追加されたとき、ベンダによる移行サポートが期待できる。

 最後にもう一点。新しいテクノロジの導入を急ぎ過ぎないこと。J2EE仕様やベンダ製品にまだ統合されていないような技術を利用すると、時に悲惨な結果を招くこともある。なぜなら、ベンダ・サポートがまったく得られないからだ。ベンダがサポートを提供しない、標準化前のテクノロジに手を付けるべきではない。結局のところ、顧客のビジネスを実現することがわれわれのビジネスであって、目新しい技術をもてあそぶことではないのである。

4. J2EEセキュリティの利用を当初から検討すること

 J2EEセキュリティを利用すること。何はともあれ、すべてのWebコンテンツとEJBにセキュリティ保護を設定し、認証済みユーザーのみアクセス可能とすべし。

 筆者らの顧客の中にも、WebSphere?に備わるJ2EEセキュリティ機能の利用をまったく検討していない例が少なからずあり、いつも驚かされる。同機能の導入を初めから検討しているのは、おそらく顧客全体の半分程度であろう。実際、大手の銀行や金融機関などの顧客がセキュリティ機能を使用していないケースもあったが、幸いなことに運用開始前のレビュー段階で対処することができた。

 ほとんどすべてのアプリケーションはセキュリティ機能を必要とする。よって、J2EEセキュリティを利用しないという選択は、J2EEベンダの製品より優れたセキュリティ・インフラを自前で開発できる、という危険なかけを意味する。しかし、分散アプリケーションのセキュリティ確保はとても厄介な問題だ。例えば、EJBへのアクセスを制限するには、ネットワーク上で安全に交換できる暗号化トークンを用いて実装しなくてはならない。われわれの経験からいえば、自作のセキュリティ・インフラは安全性が低く、実運用システムにおいて重大な脆弱性となり得る欠点を抱えている場合が多い(この問題については、Roland Barcia著「IBM WebSphere?: Enterprise Deployment and Advanced Configuration」(IBM Press、2004)の第18章を参照のこと)。

 J2EEセキュリティを採用しない理由としてよく挙げられるのが、パフォーマンスの低下を抑えたい、外部のセキュリティ製品(例えばNetegrity SiteMinder?など)を利用するので特に問題はない、といった説明である。また、アプリケーション・サーバがどのようなセキュリティ機能を提供するのかが、よく理解されていない例もある。こうした落とし穴にはまってはならない。特に注意してほしいのは、たとえ外部のセキュリティ製品が優れた能力を備えていても、それはJ2EEアプリケーション全体の安全性を保証するものではない、ということだ。こうした製品とアプリケーション・サーバの両者を統合することで、初めてシステムのすべての側面でセキュリティを確保できる。

 また、別の理由として挙げられるのが、J2EEのロール・ベースのセキュリティ・モデルでは、複雑なビジネス・ルールの実装に求められるきめ細かなアクセス制御が行えない、という説明である。これには一理あるが、J2EEセキュリティを捨ててしまう理由にはならない。こうした場合は、要求される複雑なビジネス・ルールを実装する手段として、J2EEの認証とロールのモデルを活用すればよい。つまり、J2EEセキュリティによって容易に得られる信頼性の高い認証情報(ユーザーIDとロール)を用いて、複雑なビジネス・ルールを実装するコードを記述するのである。

5. 理解できる範囲でビルドすること

 イテレーティブな開発プロセスの導入によって、J2EEの各要素を段階的にマスターすべし。アプリケーション全体を一度に構築するのではなく、個々の小さな機能ごとにビルドすること。

 J2EEは巨大である。J2EEを初めて経験する開発チームにとっては、覚えるべき概念やAPIがあまり多過ぎるため、その全体を一度に学ぶことは極めて困難だ。こうしたプロジェクトで成否のカギを握るのは、焦らず一歩ずつJ2EEを学んでいくことである。

 具体的には、アプリケーションを構成する個々の小さな機能ごとにビルドを行えばよい。まずは、簡単なドメイン・モデルやバックエンドの永続化機能(例えばJDBCなど)を構築し、全体のテストを実施する。開発チームがこれらの作業に自信を持てるようになったら、続いてそのドメイン・モデルに対するフロントエンドをサーブレットやJSPで開発する方法を学び始める。また、EJBが必要と思われる場合は、最初にシンプルなSession Facadeを作成し、それをCMP Entity BeanもしくはJDBCベースのDAO(Data Access Object)と組み合わせる。Message Driven BeanやJMSといった複雑な機能へと進むのは、その後でよい。

 これは、それほど目新しいアプローチではない。しかし、実際にこの手順に沿ってチームのスキルを高めていく事例はあまり多くはない。大半のケースでは、スケジュール上の制約により、全体を一度に開発しようとする。MVCのビューとモデル、コントローラのすべてを同時に構築するといった具合だ。こうしたやり方の代わりに、アジャイルな開発手法の導入を検討すべきである。

 例えば、XP(eXtreme Programming)では、上述したようなインクリメンタルな学習と開発を推奨しており、その中でも「Model First」と呼ばれる手法がしばしば用いられる。これは、XPにおけるユーザー・ストーリーを整理し実装するために、ドメイン・モデルから先に作成するという手法である。つまり、主要なユーザー・ストーリーに対応するドメイン・モデルをまずは構築し、その後でユーザー・ストーリー全体の実装に必要なUIを追加していくという手順だ。この方法は、チーム全体がテクノロジを1つずつ学習していくのにとても都合がよい。いくつものトレーニングを並行して受けたり、何冊もの書籍を読んだりといった大変な思いをせずに済むのである。

 また、J2EEアプリケーションのレイヤごとにイテレーティブな開発を行うことで、適切なデザイン・パターンやベスト・プラクティスを導入しやすくなる。MVCのモデル部分から構築を進め、DAOパターンやSession Facadeパターンなどを随時適用していけば、JSPなどのビュー部分でビジネス・ロジックを実装してしまうこともないだろう。

 さらに、アプリケーションの個々の機能の単位で実装することで、パフォーマンス・チューニングを早期に始めるのが容易になる。Stacy Joinesが「Performance Analysis for Java Websites」(Addison-Wesley、2002)で指摘するように、パフォーマンス・テストをプロジェクトの最後まで遅らせることは大きなトラブルの原因となるのである。


6. EJBコンポーネントを利用するときは、Session Facadeを常に使うこと

 どのような種類のクライアントに対しても、Entity Beanを直接公開してはならない。また、Entity Beanではローカル・インターフェイスのみ用いること。

 Session Facadeパターンは、EJBコンポーネントの利用に際して、最も広く定着しているベストプラクティスの1つである。実のところ、このパターンは、CORBAやEJB、DCOMなどのあらゆる分散テクノロジー分野で用いられている普遍的なルールを表したものだ。つまり、アプリケーションにおける「ネットワークの横断」をなるべく減らすという原則である。これにより、細かなデータがネットワーク上を何度も行き交うことによるオーバーヘッドの発生を防止できる。

 Session Facadeを実装するには、複数の細かなロジックを包括した大きなファサード・オブジェクトを作成し、1回のメソッド呼び出しだけで主要なビジネス・ロジックを実行できるよう設計する。その結果、ネットワークのオーバーヘッドが抑えられる仕組みだ。さらにEJBでは、ビジネス・ロジック全体を含んだ1つのトランザクションを構成できるため、データベース・アクセスの回数を大幅に減らすことができる。

 なお、Session Facadeについては、Kyle Brown氏の「Enterprise Java Programming with IBM WebSphere?, 2nd Edition」(Addison-Wesley、2003)にて詳しく説明されている。また、同パターンの基本形を解説した書籍としては、Deepak Alur氏らによる「Core J2EE Patterns, 2nd Edition」(Addison-Wesley、2003)がある。さらにほかの文献として、EJBを超えた普遍的なパターンとして解説しているMartin Fowler氏の「Patterns of Enterprise Application Architecture」(Addison-Wesley、2002)や、Floyd Marinescu氏の「EJB Design Patterns」(John Wiley & Sons、2002)がある。

 さて、EJB 2.0仕様では、同じJVM内で動作するEJBコンポーネント間のアクセス・パフォーマンスを最適化する手段として「ローカル・インターフェイス」が新たに導入された。このローカル・インターフェイスを利用するには、同インターフェイスを呼び出すようにアプリケーション・コードを書き換えなければならない。また、後になってEJBコンポーネントを異なるJVMに分散させることになれば、再びコードを元に戻す必要が生じる。そのため、Session Facade(通常はステートレスSession Bean)は、従来どおりリモート・インターフェイスを用いて設計しておくことを勧める。一方、その背後で動作するEntity Beanは、Session Facadeと同じJVM内で動作するため、ローカル・インターフェイスを利用すべきである。

 ただ実際のところ、少なくともWebアプリケーションでは、EJBクライアントとEJBコンポーネントが同じJVM内で動作するケースが多い。そうしたときはSession Facadeについてもローカル・インターフェイスを導入し、パフォーマンスを最適化すべきだろう。また、WebSphere? Application Server(WAS)の「No Local Copies(ローカルコピー禁止)」機能のようなアプリケーション・サーバの最適化設定を利用すれば、ローカル・インターフェイスを使用せずともそれと同等の効果が得られる。いずれの方法にせよ、メソッド呼び出しのメカニズムがオブジェクトの値渡し(pass by value)から参照渡し(pass by reference)に変化することに気を付けてほしい。この違いは厄介なバグの原因ともなりかねないので、ローカル・インターフェイスの導入についてはプロジェクトの当初から検討しておくべきだろう。

 一方、Session Facadeにリモート・インターフェイスを用いることのメリットは、J2EE 1.4準拠のWebサービスとしてSession Facadeを公開できることだ。J2EE 1.4のWebサービス・デプロイメントについて規定するJSR 109では、EJB WebサービスとEJBコンポーネント間のインターフェイスとして、ステートレスSession Beanのリモート・インターフェイスを使用することを定めている。Webサービスをサポートすれば、より多くの種類のクライアントからSession Facadeのビジネス・ロジックを利用できるようになるだろう。

7. ステートフルSession BeanではなくステートレスSession Beanを使うこと

 ステートレスSession Beanを用いることで、システムのフェイルオーバーをより容易に実現できる。個々のユーザーの状態を保持するにはHttpSession?を利用すべし。

 筆者らにとって、ステートフルSession Beanはすでに過去の技術である。同Beanは、いわゆるCORBAオブジェクトとまったく同じメカニズムで動作する。すなわち、1台のサーバに縛られ、最後までそのサーバに依存し続けるオブジェクトである。もしサーバがダウンすれば、クライアントにとって不幸なことに、ステートフルSession Beanが保持していた内容は失われてしまう。

 J2EEアプリケーション・サーバに備わるステートフルSession Beanのフェイルオーバー機能を使えば、サーバのダウンに対してある程度の対策は可能である。とはいえ、ステートレスSession Beanに比べれば、そのスケーラビリティの低さは否めない。例えばWASでは、ステートレスSession Beanに対するリクエストが、クラスタを構成するすべてのサーバに向けて負荷分散される。対照的に、ステートフルSession Beanへのリクエストは負荷分散できない。そのため、サーバ間の負荷にばらつきが発生し得るのである。  頑丈な分散システムを構築するための原則は、できる限りステートレスな設計とすることだ。しかしステートフルSession Beanを利用するには、アプリケーション・サーバ上にセッション情報を保持しなくてはならない。その結果システムが複雑になり、トラブル時の対応も面倒なものとなってしまう。

 こうした理由から、大半のアプリケーションについてステートレスSession Beanを選択することをお勧めする。ビジネス・ロジックの処理に必要なユーザーごとのセッション情報は、HttpSession?などの機能によりEJBの外側に保持し、EJBへのメソッド呼び出し時に引数として渡せばよい。もしくは、EJBのトランザクションの実行時に、Entity Beanなどを用いてバックエンドのデータベースから読み込む方法もある。

 また、アプリケーション・サーバのメモリ上にセッション情報のキャッシュを適宜保存することも可能だ。ただし、分散システムではキャッシュ間の整合性にまつわる問題が起こり得ることも留意すべきだろう。こうしたキャッシュのメカニズムは、データがリードオンリーのときに最も効力を発揮する。

 結局のところ、プロジェクトの当初からスケーラビリティについて計画を立てておくことが重要である。システムの設計を見直し、サーバを2台以上に増やしても問題なく動作するかどうかチェックする。このルールは、アプリケーションのコードに限らず、MBeansや管理用インターフェイスの設計にも当てはまる。

 そして、ステートフルな設計を避けること。これはJ2EEの設計の基本原則であって、IBM製品に固有の制限に由来するわけではない。例えばTyler Jewell氏の記事「Stateful Session Beans: Beasts of Burden」(OnJava?.com)では、筆者らの主張と同様に、ステートフルSession Beanへの厳しい意見が紹介されている。

8. CMT(Container Managed Transaction)を使うこと

 CMT(Container Managed Transaction)の役割は、一連の処理を組み合わせ、堅牢性を備えた1つのトランザクションを構成することである。これは、EJBコンテナのサポートがあって初めて実現可能となる、EJBの主要なアドバンテージといえる。

 アプリケーションによっては、データベース製品のトランザクション機能やjavax.jts.UserTransaction?インターフェイスを使用し、トランザクションの開始と終了を明示的に制御しているものも少なくない。しかしこの方法では、将来的にいくつかのアプリケーション・モジュールを組み合わせる場合に、トランザクション制御のコードのリファクタリングが必要となることが多い。

 例えば、モジュールAとモジュールBがあり、それぞれがデータベース・トランザクションを個別に開始し、データベースの更新とトランザクションのコミットを実施しているケースを想定しよう。ここで、モジュールCからこれら2つのモジュールを呼び出すとどうなるだろうか。モジュールCでは、1つのビジネス・ロジックを実行するために、2つの独立したトランザクションを実行しなくてはならない。また、モジュールBが処理に失敗した場合には、モジュールAの処理だけがコミット済みとなってしまい、望ましい結果が得られないのである。

 一方、モジュールAとBがいずれもCMTによって実装されているとしよう。このとき、DD(Deployment Descriptor)設定に基づき、モジュールCにおいてCMTのトランザクションが自動的に開始される。また、面倒なコードの書き換えをせずとも、モジュールAとBの処理はこのトランザクションの一部として自動的に組み込まれるのである。

 また、アプリケーションの個々の処理において、複数のデータベースやリソースにアクセスしている場合は、2フェーズ・コミットによるトランザクション管理が不可欠になる。例えば、JMSのキューからメッセージを1つ取り出し、そのメッセージを基にデータベースを更新する処理を考えよう。このような処理では、「両方の処理が完了するか、もしくは両方とも実行されないかのいずれか」でなくてはならない。もし、キューからメッセージを削除した後でデータベースの更新に失敗すると、システム全体の整合性が失われてしまい、ビジネスや顧客に深刻な影響を与えかねないのだ。

 こうした問題に対し、自作の対応策で解決しようとするアプリケーションをしばしば見かける。データベースの更新処理が失敗したとき、メッセージをキューに戻すような対処である。しかし、これはお勧めできる方法ではない。この処理の途中で障害が起きたときの対応などさまざまなケースを想定しなければならず、想像するよりはるかに実現が難しいのだ。

 このような場合は、2フェーズ・コミットを使えばよい。多くのデータベースやJMSは、2フェーズ・コミットに対応している。よって、CMTのトランザクションからこれらのリソースにアクセスすれば、上述したような面倒な処理はすべてEJBコンテナが行ってくれる。具体的には、システムやデータベースなどの障害が発生したとしても、「トランザクションのすべての処理が完了するか、もしくはすべてが実行されないかのいずれか」が保証されるのである。さらに、個々のトランザクションの状態は、トランザクション・ログに記録される。このように、複数のリソースにアクセスするアプリケーションでは、CMTのトランザクション管理により数え切れないメリットが得られるのである。

9. プレゼンテーション手段としてはJSPを最優先に使うこと

 プレゼンテーションの実装手段としてJSPを最優先に使うこと。XMLやXSLTは、コントローラとバックエンドにおいて複数のプレゼンテーション形式のサポートが必要な場合にのみ用いる。

 プレゼンテーションの実装手段にはJSPではなくXMLやXSLTを使うべき、という議論をしばしば耳にすることがある。その理由として挙げられるのは、「JSPではモデルとビューが混在しやすく、XMLやXSLTならばそれを防げる」という説明である。しかし、この説明は正しいとはいい切れない。少なくとも、問題はそれほど単純ではない。

 XSLとXPathは、ほとんどプログラミング言語といってよい。XSLはルール・ベースの言語であり、プログラマーがよく使う制御構文をすべて備えているわけではないため、プログラミング言語と呼ぶには抵抗があるかもしれない。しかし実のところ、XSLはチューリング完全な言語であり、あらゆるアルゴリズムを実装する能力を備えている。

 問題は、こうしたXSLの柔軟性を開発者が乱用しかねないことだ。JSPの場合は、モデルの機能をビューで実装できることの危険性について、共通の認識が存在する。しかし、これと同じことはXSLでも起こり得る。例えば、XSLからデータベースにアクセスするような処理も、簡単ではないが実現可能である。実際に筆者らは、モデルに匹敵する高度な変換処理を実装した、極めて複雑なXSLTスタイルシートを目にしたことが何度かある。

 プレゼンテーションの実装手段としてJSPを優先すべき根本的な理由は、それが最も広く普及しているJ2EEのビュー・テクノロジーである点にある。カスタムタグの標準ライブラリJSTLやJSP 2.0の新機能などの登場により、モデルとビューがきれいに分離されJavaコードを含まないJSPページを、大変簡単に作成できるようになった。また、WebSphere? Studioなどの開発環境にも、デバッグをはじめとするJSPサポート機能が豊富に搭載されている。

 多くの開発者にとっては、ルール・ベースのXSLよりも手続きベースのJSPの方がとっつきやすい。WebSphere? StudioにはXSL開発のサポート機能も用意されているものの、GUIレイアウト・ツールなどのJSP機能の方が、WYSIWYGスタイルのより使いやすい開発環境を提供する。これはXSLではなかなか得られないメリットであり、特にJSFなどのフレームワークを利用した開発ではその差が顕著になる。

 JSPを選択すべきもう1つの理由は、そのパフォーマンスの良さである。IBMでは、XSLとJSPのそれぞれのパフォーマンスを比較するテストが実施された。その結果、同じ内容のHTMLページを出力する際に、大半のケースにおいてJSPの方がXSL変換よりも数倍速いことが確かめられた。これは、コンパイル済みXSLを利用した場合も同じである。すべてのケースに当てはまるわけではないが、HTML生成のパフォーマンスがネックとなる状況では、XSLの遅さが問題となり得るだろう。

 ちなみに、用途によってはXSLが適している場合もある。例えば、ある決まった形式のデータを受け取り、いくつかのスタイルシートを適用してさまざまな形式で出力するという用途である。こうした用途には、XSLの能力を活用してビューを実装するのがベストな選択だろう(Martin Fowler氏の文献を参照のこと)。とはいえ、このようなニーズはあまり多くはない。HTMLページを決まった形式で出力するだけであれば、結果的にXSLを持て余し、そこから得られるメリットよりもデメリットの方が多くなるだろう。

10. HttpSession?にはビジネス・ロジックに必要な最小限のデータだけを保管すること

 セッション情報の永続化を行うべし。

 HttpSession?は、分かりやすいAPIを備え簡単に使うことができるため、アプリケーションのセッション情報の保管場所として最適である。ただ残念なことに、HttpSession?は本来「セッション情報を一時的に保持する場所」にすぎないことを忘れている開発者が少なくない。つまり、キャッシュ領域として無制限に利用できるわけではないのだ。実際に、個々のユーザーのHttpSession?に数MBもの膨大なデータを格納しているシステムを数多く見かける。このような設計では、もし1000ユーザーがログインすれば、1GBものメモリ容量をセッション情報のためだけに消費することになる。

 HttpSession?に保持するセッション情報は最小限にとどめるべきであり、さもなければパフォーマンスに影響が生じることになる。2〜4KB以下に抑えておくのがベスト・プラクティスだ。もっとも、厳密な上限があるわけではなく、例えば8KBのセッション情報を保持することも可能ではある。しかし、2KBに抑える場合に比べれば、明らかにパフォーマンスは低下する。セッション情報のサイズに常に気を配り、必要性に乏しいデータのたまり場にならないよう注意すべきである。

 また、よくある間違いとして、簡単に作り直せるデータさえもHttpSession?にキャッシュとして保存しているケースが挙げられる。アプリケーション・サーバでは、HttpSession?を永続化することで、サーバがダウンしてもHttpSession?を復元する仕組みを備えている。逆にいえば、永続化の不要なデータをHttpSession?に保存しておくと、不必要なシリアライズやディスク書き出しが発生し、大きな負荷となるのだ。

 こうしたデータをキャッシュするには、メモリ上のハッシュ・テーブルを利用し、HttpSession?には同テーブルのキーだけを格納しておけばよい。そして、サーバがフェイルオーバーした場合は、新しいサーバ上で元のデータを作り直すのである。この方法について詳しくは、Kyle Brown氏の「Improving HttpSession? Performance with Smart Serialization」(IBM DeveloperWorks?)を参照してほしい。

 また、HttpSession?の永続化は忘れずに動作させること。さもなければ、トラブルやメンテナンスなど何らかの理由によりサーバが停止した場合に、そのときアクセスしているすべてのユーザーのセッション情報が失われてしまう。これはユーザーにとってかなり不愉快な経験である。ユーザーは、再度ログインを行い、それまでの作業を始めから繰り返さなくてはならない。

 一方、もしHttpSession?の永続化を動作させていれば、ユーザーのセッション情報は自動的にほかのアプリケーション・サーバに引き継がれる。多くのユーザーは、もともとのサーバが停止したことさえ気付かないはずだ。実のところこれは便利な機能で、厄介なバグによって頻繁にクラッシュするものの、HttpSession?の永続化のおかげで十分なサービス品質を維持しているプロダクション・システムも少なくない。


11. WebSphere? Applicatioin Serverでは、ダイナミック・キャッシュ機能をオンにし、サーブレット・キャッシングを利用すること

 サーブレットのキャッシングにより、最小限のオーバーヘッドでパフォーマンスを大幅に改善できる。コードの変更も不要である。

 キャッシングは、パフォーマンスを向上する手段として広く利用されている技法だ。しかし残念ながら、現在のJ2EE仕様ではサーブレットやJSPのキャッシュについて規定していない。そこでWASでは、アプリケーションを変更することなくページやフラグメント(ページの一部)のキャッシングを実現する「ダイナミック・キャッシュ機能」を提供している。同機能では、XMLベースのDDファイルを利用して、キャッシュ・ポリシーを宣言的に指定する。よって、アプリケーション自体はJ2EE準拠のポータビリティを維持しつつも、WASに備わるサーブレットおよびJSPのキャッシングによるパフォーマンス最適化のメリットを得られるのである。

 アプリケーションの特性によっては、ダイナミック・キャッシュ機能により劇的な効果を得ることができる。例えば、J. Stan Cox氏とBrian K. Martin氏の「Exploiting Dynamic Caching in WAS 5.0, Part1」(e-Pro Magazine、July/August 2003)では、ある既存のRSS(RDF Site Summary)生成用サーブレットについてダイナミック・キャッシュ機能を適用したところ、数十倍ものパフォーマンス改善が可能になったことが紹介されている。もっとも、この事例では比較的シンプルなサーブレットを対象としているため、より複雑なアプリケーションの場合はここまでの改善は難しいだろう。

 また、このダイナミック・キャッシュ機能に加えて、WASのプラグインであるESIフラグメント・プロセッサや、IBM HTTP ServerのFRCA(Fast Response Cache Accelerator)、Edge Serverキャッシュ機能などを組み合わせれば、さらなるパフォーマンス向上が実現する。特に、読み込みの負荷の割合が大きなサイトでは、これらを活用することで絶大なアドバンテージを得られるはずだ。これらのパフォーマンス最適化について詳しくは、R. Willenborg氏ほかの「Designing WebSphere? Application Server for performance: An evolutionary approach」(IBM Systems Journal Volume 43、No. 2、2004)、およびR. Bakalva氏ほかの「WebSphere? Dynamic Cache: Improving WebSphere? Performance」(IBM Systems Journal、Vol. 43、No. 2、2004)を参照してほしい。

12. O/Rマッピングの手段として、まずはCMP Entity Beanを利用し、生産性を高めること

 アプリケーション・サーバに備わるCMP Entity Beanの最適化機能(先行読み込み、各種キャッシュ・オプション、隔離レベルなど)を利用すべし。その後、Fast Lane Readerパターンなどを適宜導入し、パフォーマンス目標を達成する。

 O/R(Object/Relational)マッピングは、エンタープライズ規模のJavaアプリケーション構築を支える基盤技術である。ほとんどすべてのJ2EEアプリケーションは、何らかの形でO/Rマッピングを必要とする。そこでサーバ・ベンダーは、各種ツールや標準仕様によって広くサポートされているポータブルなO/Rマッピング機能を用意した。それが、EJBのCMP(Container Managed Persistence)機能である。

 CMPが実装され始めたころは、そのパフォーマンスの低さやSQL機能の少なさから、あまり良い評判が得られなかった。しかし、改良の施されたEJB 2.0および2.1仕様が制定され、IBM WebSphere? Studio Application Developerをはじめとする対応ツールが登場すると、それまでのCMPの評価は覆されることになった。

 いまや、高いパフォーマンスを要求されるアプリケーションにおいても、CMP Entity Beanが数多く導入されている。それを可能にしたのは、キャッシュのライフタイム管理や先行読み込みなど、アプリケーション・サーバに備わるCMP Entity Beanの最適化機能である。これらの最適化はDDにて指定するため、アプリケーション・コードの変更は不要で、ポータビリティにも影響しない。

 キャッシュのライフタイム管理とは、CMP Entity Beanの内容をキャッシュに保存し、一定時間が経過したらそれを破棄するメカニズムである。これにより、CMP設定の「Option A」と同様のキャッシングによるパフォーマンス向上を実現しつつ、同オプションが苦手とするクラスタリングによるスケールアウトが可能になる。一方、先行読み込みとは、CMR(Container Managed Relationship)と組み合わせて用いる最適化機能である。具体的には、マスター側テーブルを検索する際に、ディテール側テーブルのレコードも先行して取得するメカニズムだ。CMRではマスター側に引き続きディテール側テーブルを検索することが多いため、そうした状況のデータベース・アクセスを最小限に抑えられる。これらの最適化機能とそのパフォーマンス・メリットについて詳しくは、Harvey Gunther氏の「Optimizing WebSphere? 5.0 Performance Using EJB 2.0 caching and Read-Ahead hints」(WebSphere? Developers Journal、March 2003)を参照してほしい。

 また、CMP Entity Beanの能力を最大限に引き出すには、トランザクションの隔離レベルに留意すべきである。すなわち、データの整合性を維持できる範囲で、可能な限り隔離レベルを低く設定しておく。隔離レベルが低いほどパフォーマンスが向上し、データベースのデッドロックのリスクも抑えられる。

 こうしたCMP Entity Beanの使いこなし方は、最も議論が集中する部分だ。これまでにも、CMP Entity Beanを称賛したり批判したりする記事がいくつも公表されている。ただ、根本的な問題は、データベース開発がそもそも一筋縄ではいかないという点にある。つまり、どのような手段でデータベースを利用するにせよ、データベース検索やレコードのロックの動作についての基礎知識が不可欠だ。よって、CMP Entity Beanを用いる場合は、上述したKyle Brown氏の書籍やRoland Barcia氏ほかの「IBM WebSphere?: Enterprise Deployment and Advanced Configuration」(IBM Press、2004)などを参考にして、その扱い方を習得してほしい。ロックやアクセス競合のメカニズムを理解するのは容易ではないが、十分な時間と労力を費やせばマスターは可能だ。

 この記事では、J2EE開発における失敗を避けるためのデザイン・パターンやベスト・プラクティスについて紹介した。これらの実践に必要なすべてを解説できたわけではないが、次に何をすべきかを知るための指針は示せたはずである。