
CDIが業務システム開発にもたらすメリットとは? EJBとの使い分けは?──最新Java EE開発“虎の穴” 第3回 上妻宜人氏
「Java EE 6で導入されたCDIの利点は"テスト容易性"と"技術依存の排除"の実現」だと語るのは、NTTコムウェアでJava EE開発や技術支援に携わる上妻宜人氏だ。具体的にどのようなメリットがあるのかを聞いた。
CDIはJava EE開発者待望の標準DI機能。エンタープライズ・システム開発における最大の効用は"テスト容易性"と"技術依存の排除"
Java EE 6で新たに導入された「CDI(Contexts and Dependency Injection)」は、多くの開発者が待ち望んでいた標準DI(依存性の注入)機能だ。Java EE 5のEJB 3で先行導入されていたDI機能が、サーブレットやJSFなどを含むJava EEアプリケーション全体で利用可能になったのである。
このCDIの導入は、エンタープライズ・システム開発にどのような恩恵をもたらすのだろうか。NTTコムウェアの品質生産性技術本部に所属し、日頃Java EEによるミッション・クリティカル・システムの開発や技術支援に携わる上妻宜人氏(技術SE部 OSS・AP技術担当)は、「技術的な観点で見た最大の効用は、"テスト容易性"と"技術依存の排除"の実現」だと語る。具体的にどのようなメリットが得られるのかを上妻氏に聞いた。
CDIで、なぜJava EEアプリケーションのテストが容易になるのか?

NTTコムウェア
品質生産性技術本部
技術SE部OSS・AP技術担当
上妻宜人氏
──上妻さんはこれまで、業務プロジェクトなどを通じてさまざまな用途でDIコンテナを扱われてきましたが、Java EE 6の標準機能としてCDIが導入されたことを、どう評価していますか?
私たちSIerにとって、Java EE開発で広く利用できる"標準DI機能"の導入は非常に喜ばしいことです。なぜなら、2000年代前半にDIコンテナが登場して以来、安心して商用サポートを受けられる製品がなかったからです。このことは、特にミッション・クリティカルなシステムでの利用において、大きなマイナス・ポイントでした。
しかし、Java EE 6でCDIが標準機能となったことで、今後はオラクルさんのようなアプリケーション・サーバ・ベンダーからサポートを受けられるようになります。DIコンテナ製品の選択肢の幅も広がりますし、有り難いことです。
──Java EEの標準機能になることには、そのような利点があるのですね。一方、CDIの技術的な効用として、上妻さんはテスト面でのメリットを強く感じておられるとか。
CDIに関しては、一般的なJava EEアプリケーションを構成するWeb層、ビジネス層、永続化層の間を疎結合にする仕組みとして使えるという利点がよく語られます。もちろん、そのことは間違いないのですが、それがもたらす具体的な恩恵として、私は"テスト容易性"に注目しています。
今日のシステム開発は、従来よりもプロジェクトの期間が短くなり、サービスインした後に改修や拡張が入るサイクルも同様に短期化しています。そうした中で、アプリケーションの開発や改修/拡張を行った際にテストが簡単かつ効率的に行えるというのは非常に大きなメリットであり、これがプログラムの品質向上や開発コストの削減にもつながるのです。
──CDIを使うことで、どのようにテストが容易になるのでしょうか?
Restful Webアプリケーションを例にとってご説明しましょう。この場合、Web層にはRestエンドポイントを配置し、ビジネス層にサービスの実装クラスを、その背後の永続化層には、DBMSにアクセスするDAO(Data Access Object)や他のシステムに接続するJAX-WSクライアント、メール・サーバに接続するメール・クライアントなどを配置します。

これまで、こうした構成のアプリケーションで、Web層からビジネス層に、あるいはビジネス層から永続化層にアクセスする際には、各層のクラスを直接指定して生成する(newする)コードが書かれていました。この"newの連鎖"、すなわち"依存性の連鎖"により、各層が密結合していたのです。

このようにnewで各層を直接的につなぐ設計では、さまざまなデメリットが生じます。最大のデメリットは「テストに手間がかかる」ということです。各層の特定部分をテストしたいだけなのに、各層が密結合しているために、すべての層の実装クラスを用意しなければならなくなるからです。

システム開発プロジェクトでは、アプリケーションの開発とデータベースなど外部リソースの構築が別々に行われることが多く、しばしば「アプリケーションの実装は終わったけど、データベースの構築が完了していない」といった状況が起こります。とは言え、リリース時期が迫る中で十分なテストを行えないと困るので、テスト用のフェイク・クラスやモック・フレームワークを使ってテストするといったことが行われています。しかし、各層がnewで密結合している場合、ソースコードを修正せずに実装クラスをテスト用クラスに差し替えるのは困難です。
また、newによる密結合を緩和するイディオムとして、実装クラスの代わりにインタフェースに依存させるというテクニックや、サービス・ロケーター・パターンによる実装も使われてきました。インタフェースの型を介して実装クラスを呼び出すことで、実装クラスへの直接的な依存をなくそうというわけです。

ただし、インタフェースを介した場合でも、"依存性の連鎖"を完全に断ち切るのは困難です。インタフェースを使ったとしても、次のようなコードでは結局、単体テストの際、永続化層などに実装クラスや実リソースが必要になってしまうのです。
【インタフェースを介した場合でも、“依存性の連鎖”を断ち切るのは難しい】
public class ServiceImpl implements Service {
...
@Override public void doBusiness() {
// 実装クラスでデータベースにアクセスする
dao.select();
}
}
@Path ("/sample")
public class SampleResource {
public Entity sampleMethod () {
// 自分でローカル変数にnewすると、ユニット・テスト時に
// データベースにアクセスしてしまう
Service service = new ServiceImpl();
service.doBusiness();
...
近年は効率良く開発を行うために、Jenkinsなどを使って継続的インテグレーション(CI:Continuous Integration)環境を構築し、テストを自動化しているプロジェクトも多いでしょう。しかし、せっかくCI環境を使って開発を効率化しても、"依存性の連鎖"によって密結合したアプリケーションでは、テストの準備に多くの手間がかかったり、最悪の場合には準備の時間が十分にとれず、納得がいくまでテストが行えなかったりといったことすら起きる可能性があります。
それでは、この問題を根本から解決するには、どのような仕組みがあればよいのでしょうか。"誰か"が、アプリケーション実行時に必要なオブジェクトを探し出してインスタンス化し、自動的に参照先に入れてくれるとよいでしょう。それすれば、newによる密結合をなくすことができます。それをやってくれる"誰か"が、CDIなのです。
──CDIにより"依存性の連鎖"を断ち切れるわけですね。
CDIでは、プログラム実行時に、フィールドに定義された型に適合する実装クラスをアプリケーション・サーバなどのDIコンテナが探し出してインスタンス化し、それを参照先としてインジェクト(Inject:注入)してくれます。生成したインスタンスから参照される先のクラスもDIコンテナが探し出してnewしてくれるので、newを直接書くことによるレイヤ間の密結合を防ぐことができます。

単体テストの際にも、テスト・ケースにおいて実装クラスをテスト用クラスに差し替えれば、プログラムを修正せずに各層を単独でテストすることが可能です。CIなどによる自動テストも容易になるので、短期間で効率的にテストを行い、プログラムの品質を高められます。

これまでの業務経験から、私は「エンタープライズ・システム開発の世界では、テストしやすいフレームワークの上でアプリケーションを書くことが、とても重要だ」と感じてきました。CDIにより、Java EE開発の世界でも、それを実現できるようになるのです。