BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル Java 7モジュール・システムの懸念点

Java 7モジュール・システムの懸念点

ブックマーク

最近、新しいJavaモジュール・システムが注目を集めています。DevoxxでのJigsawプロジェクトについてのプレゼンテーションを見たあと、JAR hellなどの複雑なクラスパス・バージョニング問題に対する解決法になり得るだろうと思い、胸が踊りました。開発者はようやく、推奨メカニズムの使用を強いられることなく、好みのバージョンのXalanの使用が可能となるでしょう。残念ながら、より効率的なモジュール・システムへの道はさほどクリアではありません。

実際の問題を検討する前に、いくつかの基本的なコンセプトをみてみましょう。
 

モジュラリゼーション

モジュラリゼーションは複雑性に対処するのにふさわしい手段です。これはアプリケーションをパーツ(モジュール、ライブラリ、バンドル、サブ・プロジェクト、コンポーネント)に分割し、それらを個別に攻略するのに好都合です。モジュラリゼーションの最終的な目標はモジュール間の通信に使われる定義されたAPIセットを備えることにあります。
 

全てのモジュール間通信がこのAPI使用によってのみ実行されれば、モジュールは疎結合となるので
 

  • モジュールの実装が容易になります
  • モジュールを個別に開発およびテストすることが容易になります
     

これはオブジェクト指向パラダイム(OOP, object oriented paradigm)に類似しています。OOPでは、小さく、再利用可能で、シンプルな、十分に分離したオブジェクトが多くあることが理想の状態です。意図や動機は全く同じで、スケールのみが違うのです。
 

論理的分離
 

従来、Javaでモジュール性を実現する方法には2つのアプローチがあります。論理的分離は最も自然な方法です。これはアプリケーションを論理モジュール(サブ・プロジェクト)に分割しつつも、1つのアプリケーションとして展開することから成り立っています。適切なパッケージ構造を定義して論理的分離を完成させることも可能ですが、アプリケーションをいくつかのアーカイブ(JAR)に分割することのほうが一般的です。論理的分離はモジュールの再利用を容易にし、モジュール間の結合をより弱くするのに役立ちます。APIを規定し、全てのモジュール間の通信を所定のAPIを使用して実行されなければいけないと宣言することも可能です。このコンセプトには、1つの大きな誤りがあります。この制限を課することは難しいのです。このAPIの使用を実行するメカニズムは存在しません。パブリックAPIの一部である所定のモジュールやクラスによってのみ使用されるべきクラスを識別する方法はありません。あるクラスが「パブリック」であるならば、モジュールがどの要素であるにかかわらず、他のどのクラスによっても使用が可能です。一方、protectedあるいはpackageの可視性はモジュール内部で使用するには制約がありすぎます。通常モジュールはいくつかのパッケージから構成され、パッケージ内のクラスは互いに呼び出しが可能である必要があります。従ってアプリケーションがいくつかの論理モジュールから構成されているとしても、モジュールは通常は結合されているため分離はほぼ実用的ではないのです。
 

物理的分離

もう1つの従来のアプローチは物理的分離です。アプリケーションをコンポーネントに分割し、各コンポーネントを個別のJVMに展開することで分離を実施することが可能です。そしてコンポーネントはRMI、CORBA、またはWebServiceといったリモート・ファシリティを使用して通信します。このように分離と疎結合が実行されます。デメリットはオーバーヘッドが大きいことです。分離を行うためだけにリモートを使用するのは過剰です。進行と展開を不必要に複雑にします。パフォーマンスへのインパクトも無視できるものではありません。
 

モジュール・システム

モジュール・システムは論理的・物理的分離の中間ほどに存在します。これはモジュール分離を実行しますが、モジュールは同じJVMに展開され、それらの間の通信はシンプルでなじみあるメソッド呼び出しから成ります。そのためランタイム・オーバーヘッドはありません。Javaエコシステムで最もよく知られているモジュール・フレームワークはOSGiです。これは数種の実装が存在する十分に整備された仕様です。OSGiではモジュールはバンドルと呼ばれ、各バンドルは1つのJARに相当します。それぞれのバンドルはまた、どのパッケージがエクスポートされ、どのパッケージがインポートされるのかを宣言するMETA-INF/MANIFEST.MFファイルを含んでいます。エクスポートされたパッケージからのクラスのみが他のバンドルに使用され、バンドル内の他の各パッケージは内部的でそのクラスはそのバンドル内でのみ使用されます。
 

例えば以下の宣言を考えてみましょう。

Manifest-Version: 1.0
Import-Package: net.krecan.spring.osgi.common
Export-Package: net.krecan.spring.osgi.dao
Bundle-Version: 1.0.0
Bundle-Name: demo-spring-osgi-dao
Bundle-SymbolicName: net.krecan.spring-osgi.demo-spring-osgi-dao

これはnet.krecan.spring.osgi.commonパッケージのクラスを必要とし、net.krecan.spring.osgi.daoパッケージからクラスをエクスポートするdemo-spring-osgi-daoというバンドルを指定しています。言い換えると、この宣言は他のモジュールはnet.krecan.spring.osgi.daoパッケージのみを使用できると述べているのです。逆の言い方をすれば、このモジュールはただuse net.krecan.spring.osgi.commonパッケージを必要とするだけであり、パッケージをエクスポートするモジュールを提供するかはOSGi次第なのです。もちろんインポートとエクスポートの両方において2つ以上のパッケージを宣言することは可能です。

注目すべき大切なことは、OSGiのモジュール性はJavaの上に構築されているということです。言語の一部ではありません!モジュール分離はGUIによって実行可能ですが、コンパイラによって実行されるのではありません。OSGiコンテナはOSGiベースのアプリケーションの実行を必要とされます。コンテナはSpring DMサーバにおけるようなランタイム環境の一部となるか、アプリケーションに組み込まれることが可能です。コンテナは分離を実施するだけでなくセキュリティ、モジュール管理、ライフサイクル管理などのサービスを提供します。OSGiにはそのほか多くの興味深い機能がありますが、それは本稿で述べるところではありません。

OSGiと部分的に重複したJSR-277の提言に関する議論は多くあります(参考記事)。JSR-277が断念されJava 7の一部である新しいモジュール・システムが導入されるまで何ヶ月もの間、双方の専門家たちはどちらがより優れているかを主張してきました。
 

JSR-294

新モジュール・システムの基部はsuperpackageとしても知られているJSR-294(リンク)です。この仕様はJava言語のモジュール部分のコンセプトを構成しています。

 

JSR-294は新しい可視性のキーワード「module」を導入しました。もしメンバがこの可視性を持っていれば、同じモジュールのメンバからしか認識されないということです。これは、そのモジュール自身だけが使用することを意図した内部APIの作成を可能とします。私の考えでは、「public」キーワードはパブリックAPIが宣言された時のみ使用されるはずです。ほか全てのケースでは、「module」あるいはさらに制約がある可視性が使われるべきです。もちろん、言語に「module」キーワードがあると直ちにコンパイラがモジュール間の可視性制約をチェックします。
 

JSR-294はまた、依存性の定義も可能にします。これは所定のバージョンの中で、あるモジュールが別のモジュールによって決まることを定義することができます。
 

//org/netbeans/core/module-info.java
@Version("7.0")
@ImportModule(name="java.se.core", version="1.7+")
module org.netbeans.core; 

後半部分は「org.netbeans.core」というモジュールは「java.se.core」のバージョン1.7以上に依存するという意味です。これはMaven依存性やOSGiインポートと同等です。シンタックスについては変わる可能性が高いので気にしなくてよいでしょう。ここで大切なことは、モジュールの依存性はmodule-info.javaファイルで定義されクラス・ファイルにコンパイルされることです。OSGiでは依存性はプレーンなテキスト・ファイルで定義されます。
 

Project Jigsaw

Project Jigsaw(リンク)は提案されているモジュール・システムの第2の部分です。これはSunのJSR-294の仕様実装でしょう。しかし、これはSun JDKのモジュラリゼーションでもあります。モノシリックなJDKモジュラを作る必要があるため(参考記事)、Sunはスタンダード・ライブラリをモジュールに分割することを望んでいます。これにより直接JREでプロファイルを容易化することができます。Swingを除く全てを含んだフルJREを携帯電話に装備することが可能となるでしょう。また、全プラットフォームの新リリースを待つ必要なく、新しいスタンダードAPIを言語に導入することもできるでしょう。
 

しかし、ここに私の第1の懸念があります。Mark Reinhold氏が指摘するように、独自に開発されたJigsawとJSRスタンダードの線引きが明確ではありません(リンク)
 

この取り組み(Jigsaw)は、必然的に、JDKのモジュール化に厳密に焦点を当てる
デザインのシンプルで下位のモジュール・システムを作成するでしょう。このモジュール・システムは開発者が独自のコードを使うのに利用できるようになり、またSunもフル・サポートを行うでしょうが、Java SE 7 Platform Specificationの公的なものではありませんし、他のSE 7実装のサポートはない恐れがあります。
 

このメッセージは不明瞭で説明の余地が残されています。モジュールの作成は可能だが、そのモジュールはSun JREのみを使用するということでしょうか。もし開発者が@ImportModule(name="java.se.core", version="1.7+")と記述すると、Sun JRE上では動くがIBM JREはサポートしないということでしょうか。SunはそのJREをある方法で分割するがOracleは他の方法を用いるということでしょうか。「write once, run anywhere」(「一度書けば、どこでも実行できる」)の原則とならないことを期待しましょう。
 

この問題点はさらに難しいものであるようにすら見受けられます。Project Jigsawの主たる目的が何かということが明確ではありません。主要なゴールはSun JREのモジュラリゼーションであると言われていますが、このケースでは言語の変更は全く必要ありません。SunはJavaを言語として変化させることなくJREをモジュール化できるのです。
 

これらの言語変化はSun JREモジュラリゼーションの単なる副産物なのでしょうか。もしこれが本当ならばそれは間違っています。言語変化はファースト・クラス・シチズンであるべきであり、何らかの独自の成果の副産物ではないのです。
 

依存性

他に私が懸念していることは依存性についてです。モジュール・システムが依存性を取り扱うならば、クラスパスはもはや必要とされません。ある意味これは素晴らしいことです。クラスパスがいわゆるJAR hellと呼ばれるものにつながることは多いです。一方、クラスパスは極めて柔軟性が高いです。クラスパスをスタティック・モジュール依存で置き換えることはできないのではないかと思います。なぜなのかを見てみましょう。
 

デプロイ・タイム依存性

Javaには2つのクラスパスがあります。ビルドパスというビルド・タイムに用いられるものとクラスパスというランタイムに用いられるものです。これらはほぼ同じものですが、完全に同じではありません。典型的な例はJDBCドライバです。ビルド・タイムでJDBCドライバを特定することは必須ではありません。JDBCインターフェイスはコアJavaライブラリの一部です。しかし、ランタイムでクラスパスにJDBCドライバがあることは必要です。今日、プログラマがデータベースを変更したいとき、プログラマは単にドライバのクラス名をコンフィグレーション・ファイルで変更し、ドライバjarファイルをクラスパスに追加するだけで済みます。もし全ての依存性がコンパイル・タイムで指定される必要があるとすると、これは不可能です。もちろんJava EEではJNDIデータ・ソースを使うことができますが、Java SEと近いものはありませんし、JDBCドライバが変更されなければいけない度にアプリケーションをリコンパイルすることは実行可能なソリューションではありません。
 

リコンパイルが不可能なことですらよくあります。一部の組織では、アプリケーション・アセンブラと呼ばれる人が最終アプリケーションをモジュールからアセンブルします。この役割を行う人はソース・コードを持っておらず、ただJARを集約してコンフィグレーション・ファイルを変更し、最終パッケージを作成します。アプリケーション・アセンブラ(リンク)の役割はJava EEの仕様でも定められています。
 

オプショナル依存性
 

同様の問題はオプショナル依存性です。log4jのようなロギング・フレームワークに取り組んでいるとしましょう。このライブラリはJMSのログをとることができるので、JMSパッケージはビルドパスにある必要があります。しかしユーザの99%はJMSロギングを使用していません、ですからクラスパスに依存性を必要としません。このような状況に対応する何らかのメカニズムが存在すべきです。モジュールを構築するためにはライブラリが必要ですが、この依存性はエンド・ユーザにとって任意のものです。もちろん、完璧な世界ではJMSの機能性は個々のモジュールにありますが、私達は完璧な世界に住んでいるわけではなく、そのようにプロジェクトを分割することは実際的ではないことがあります。
 

依存性コンフリクト

別の大きな問題は依存性のコンフリクトです。Mavenで作業を行っている読者ならば、私が何について話しているかお分かりでしょう。平均的な企業アプリケーションは多数のサード・パーティのライブラリで構成されています。それらは全て依存性をもっており、この依存性がコンフリクト状態にあることがあります。例えば、ある開発者がcommons-collections 2.1.1に依存するHibernateを使いたいとします。この開発者はcommons-collections 2.1に依存するcommons-dbcpの使用も望んでいます。この開発者またはアプリケーション・アセンブラはこの状況でどうすべきかを決めなければいけません。彼は、アプリケーションのいずれの場所でも特定バージョンのライブラリ1つのみを使用するか、アプリケーションのあらゆる場所で異なるバージョンを使うことで十分であるかの、いずれかを決定できます。重要なことは、この状況は自動的に解決されることはないということです。このことは、モジュールがあるアプリケーションでどのように使用されるか、そしてどのようにバージョン間に起こりうる不適合を認識するかということを知っている人物が決めなければいけません。
 

Javaの依存性について本稿の範囲を超えて言えることは多くありますが、留意すべき主要なポイントは、依存性はスタティックではないということです。アプリケーションを一揃いのライブラリで構築し、異なるセットのライブラリで実行できます。それぞれのモジュラ・システムはこの状況を何らかの方法で対処しなければいけません。Mavenには依存性の設定方法、依存性のコンフリクトの対処法などについて多くのコンフィグレーション・オプションがあります。しかしこれは、未だビルド・システムに過ぎません。最悪のケースではクラスパスを手動で設定することもまだ可能です。OSGiは反対の状態にあります。これはランタイム(デプロイ・タイム)依存性のみに対応しますが、ビルド・タイムには対応しません。新しいJavaモジュール・システムはビルド・ランタイム双方に使用されます(と私は推測します)。この事は、すでに複雑な問題にさらに複雑なことをもたらします。
 

結論

もちろん私はSunのエンジニアがJavaをだめにしようと思っているとは考えていません。私は、彼らがJavaをより優れて使い易いものにしようと望んでいることを知っていますが、政治的あるいはマーケティング的な要因が技術的な要因より強大になることを恐れているのです。もう一度いうと、これは単なるAPIの変更やSunの仕様変更ではありません。これは言語の変更なのです。そしてひとたび言語が変更されると、「module」キーワードが追加され、元に戻す術はありません。Javaにモジュール・システムが存在するようになり、私達は好むと好まざるとに関わらずそれを使用しなければいけません。モジュラJVMがあり、言語に「module」キーワードがあり、その上で依然としてOSGiを使用しているという状況を想像するのは困難です。

参考資料

Lukas Krecan氏はフリーランスのJava EEディベロッパです。彼は実在の企業で働いていますが、オフの時にはプログラミングや想像上のパーフェクト・ワールドについてブログを書いています(リンク)

 

原文はこちらです:http://www.infoq.com/articles/java7-module-system
(このArticleは2009年1月28日に原文が掲載されました)

この記事に星をつける

おすすめ度
スタイル

BT