OSGiがエンタープライズアプリケーションに与えるインパクト

Eclipseベースのツールを開発し始めてから3年近く立ちました。1年半くらい前に、SeasarプロジェクトでOSGiサポートをしないのはなぜだろうと、はてダに書いたところ、OSGiはプラットフォームを構成するにはとてもよい仕様だけれど、Webアプリケーションには過剰な仕様だ、と、とある方から答えられました。それからもずっとなんだかんだでOSGiと関わってきましたが、だいぶ状況も変わってきました。まずモバイルやEclipse以外の環境でも広くOSGiが使われるようになりました。そしてプラットフォーム以外での利用も考えられるようになりました。プラットフォーム以外での利用は、エンタープライズアプリケーションの開発モデルを変えるだけのインパクトを起こす可能性が見えてきたのでまとめてみます。OSGiについて、どういう技術か、気になる方はOSGiの記事を参照してください。

どんな分野でOSGiが使われるようになったか

まず、どんな分野で使われるようになってきたか見ていきましょう。

例1:アプリケーションサーバ

アプリケーションサーバの分野では、JEE6のWebプロファイルのサポートのため、これまでよりもより進んだモジュール構造が求められるようになりました。そのため、OSGiコンテナ上にモジュールを配備するアーキテクチャを取られるようになりました。
これまでもアプリケーションサーバの分野では、JARを分割し、それぞれのJARをモジュールとして管理/構成されてきました。しかし、実際はそううまくモジュールに分割できていなかったそうです。*1JARとJARに物理的に分割していても、論理的に分離できないクラスリークを起こしてしまう事がありました。これは、普通のJVM環境のような一枚岩なクラスローダ環境では、モジュールの分離可能性を確認しづらいからです。同じクラスローダ上にモジュールが展開された状態では、仮にあるモジュールAとモジュールBが循環参照を起こしていたとしても、普通に動かせます。循環参照を検知する仕組みがないのです。実際にモジュールAとモジュールBを分離しよう、としたとき、初めて循環参照に気づいて分離ができないのです。

対してOSGi環境では、モジュールであるBundleごとにクラスローダを用意されます。そのため、モジュールの循環参照を検知できますし、モジュールとして分離できなくなっているような、強く依存した構造のモジュール関係を取れません。こういった制限が、モジュールの開発を容易にしたため、アプリケーションサーバでのOSGi採用を推進させてきた訳です。

例2:プラグイン機構

次に例をあげるのはプラグイン機構です。モジュールであるBundleごとにクラスローダが用意される構造は、プラグイン機構にも都合が良く、アトラシアン社のJIRAやConfluenceのプラグイン機構としても採用されています。Grailsの次期メジャーリリースからはGrailsのプラグインもBundleベースになるようですし、Maven 3もプラグイン機構部分にOSGiを採用しています。Hudsonでも採用が検討されていると聞きました。

OSGiをプラグイン機構に採用する理由は、Bundleごとにクラスローダが用意されるためです。Bundleごとにクラスローダが用意されれば、利用するライブラリ空間をBundleごとに構成することができます。ライブラリ空間が異なる構造は、例えば異なるBundleで同一ライブラリ、違うバージョンの利用する構造を取ることができるため、他のBundleへの影響を少なくすることができます。これは同一のクラスローダでは、JVMの制限により、異なるバージョンのJARを読み込む事ができないといった制限を乗り越える事を可能にしました。

ツールサポートの強化

それから、サポートしているツールも以前と比べ、だいぶ増えました。EclipseはPDEという、Eclipseに特化したプラグイン開発環境がありますが、OSGiのアプリケーション開発環境としては、とっつきにくい面もありました。しかし、Mavenベースのツールの登場や、他のIDEでの開発環境の採用が増えたため、開発が楽になってきています。
プラグイン機構にOSGiが採用されるにつれ、プラグイン開発のため、意図せずにOSGi開発をしている開発者が増えました。しかし、いざ開発を始めると、NoClassDefErrorに良く会ったり、プロパティファイルなどのJAR内のリソースを読み込めない、という問題がよく発生しました。OSGiのコアである、Bundleという構造*2と、BundleContextを介したサービス連携という仕組み自体はシンプルです。しかし、Bundleという構造は、クラスやリソースの読み方に影響があります。一枚岩開発のように、隣にあるJARの中身を参照する事はできません。そのため、NoClassDefErrorの頻発しました。この辺りが敬遠されてきた理由です。しかしBNDやBundlorなどの補助ツールを使えば、公開されていないパッケージは、コンパイル時に検出することができるため、そういったErrorが飛んでくることをかなり減らす事ができます。
また、個人的にMavenベースのため、食わずギライしていたPax Constructは使ってみて本当に素晴らしいツールだと感じています。MavenリポジトリからBundleを検索して落としてくるコマンドラインが用意されていた辺り、Rubyのgemなどのパッケージシステムに勝るとも劣らないパッケージ管理が、Javaにもあるんだ、と知った感じです。*3Pax Constructに内包されているPax Runnerを使えば、稼働させているOSGiコンテナですらも切り替える事ができます。OSGiコンテナも、R4仕様レベルであれば、高い互換性があります。*4IDEのサポートもIntelliJ IDEAもあればNetbeansもサポートしています。*5

アプリケーションをOSGiコンテナ上に構築してみると・・・。

さて、そろそろプラットフォーム以外での開発にフォーカスを移していきましょう。
Bundleというモジュールの構造では、Bundle下のパッケージの公開/非公開をコントロールできます。公開されていないパッケージ下にあるクラス・インタフェースは、他のBundleから基本的に参照できません。これまでの一枚岩開発では、JARに分割して開発を進めていても、publicなクラスであれば、他のJARからも参照できたため、どれだけインタフェースベースで開発を進めても、直接実装を参照される事ができてしまいました。直接実装を参照されてしまうと、その実装に依存した開発ができるので、実装の変更が難しくなります。例えば、

public interface HelloService{
 public void greet();
}
public class HelloServiceImpl implements HelloService{
 public void greet(){
  ...// なにかの処理。
 }
 public void greetingEcho(){
  ...// なにかの処理。同じJARの中で違うパッケージにある他のクラスにだけ利用されたい。
 }
}

...他のBundleのコード
HelloServiceImpl service = new HelloServiceImpl();
service.greetingEcho(); // OSGi環境であれば、エラー。

上記の例は、一枚岩開発であれば、HelloServiceImplのpublicメソッドにも触れてしまいます。こういった事ができないよう、公開範囲をよりコントロールできるようになります。つまり、Javaの世界でこれまであるようでなかった、Bundle(JAR)単位でのカプセル化が実現されます。OO厨の方には、萌ポイントだと思います。

Bundle単位でのカプセル化の与える影響の大きさ

Bundle単位でのカプセル化が与える影響の大きさは、とても大きいものになるでしょう。

一つは、よりインタフェース(仕様)に対してコードを書く設計になるようになるでしょう。提供される実装は別のBundleに分離されていれば、公開されているインタフェースに対して実装を書くしかなくなります。インタフェースを意識した設計になれば、そのインタフェースを実装した全く違う実装がいくつもある、という事も可能になります。これは実例として、OSGiコンテナや、OSGiの仕様に対する実装、例えばDSもApache Felix SCRというサブプロジェクトとEquinox DSなど、複数実装があります。これらはどれもインタフェース(仕様)を満たしているので、それぞれ交換可能にできています。
2つ目は、インタフェースを切り出せるのであれば、開発拠点ごとに別々に分かれてBundleを開発する、といった事も実現ができます。
Bundleが提供する実装は、仕様を満たす限り、個別に改良を加える事が可能です。
3つ目は、実装の切り離された状態でテストを書く事になるので、ユニットテストレベルでは、振る舞いをスタブしてテストを書く事になります。これにより、テスト容易性が向上するのです。
4つ目は、インタフェースが同じであれば、再利用がさらに有効に効くようになります。これまではオブジェクト指向で設計していたとしても、直接実装を参照してしまえば、交換はその実装を新しい/古いバージョンに切り替える事くらいしかできませんでしたが、インタフェースを挟む事で、裏にある実装はなんでも気にしなくてもよくなります。例えばDeclarative Serviceの実装は、FelixでもEquinoxでも動作させることができます。
5つ目は、OSGiの特徴の一つである、サービス間連携は、外部からのサービスの注入もありますが、サービスの提供もできるようになります。これは、Javaの開発では一般的になったDIは外部からの注入のみの単方向だったのに対し、双方向でサービスの注入が可能になりました。これは、よりBundleの再利用を進める方向に力が働くでしょう。例えばサーバ側と、クライアント側で同じロジックを持ったBundleを利用しやすくできます。
こういったカプセル化により、結合を疎にして実装を切り替えるのを可能にしたり、実装を再利用するのは、オブジェクト指向の利点として語られる事が多かったですが、実際は、モジュールとして分離できてこなかったため、うまくいきませんでした。しかし、こういった事はOSGiを基盤にすれば、実現できるのです。
こういった利点を享受するためには、インタフェースの切り出し方法が気になるはずですが、これは後日まとめます。

OSGiに展開することでより獲得できる成長性

突然ですが、UNIXという考え方、という本をご存知でしょうか?

UNIXという考え方―その設計思想と哲学

UNIXという考え方―その設計思想と哲学


この本は、UNIXの設計思想をいくつかの原則として書かれています。この本の原則の中に、「一つのプログラムには一つのことをうまくやらせる」という原則があります。オブジェクト指向の原則にも「単一責務の原則」という原則がありますよね。「単一責務の原則」は、クラスが担う役割は一つに限定するという原則ですが、「一つのプログラムに対して、一つのことをうまくやらせる」原則もこれに似ています。一つのプログラムに対して、一つのことをうまくやらせ、複雑なシステムは、そのプログラムを組み合わせて構築する、という考え方です。この考え方はそっくりそのままOSGiの世界でも生かせます。一つのBundleには一つのことをうまくやらせ、複雑な事は、Bundleを組み合わせる事でシステムを構築するのです。ここまで昇華できている実例は、ちょっと1000個のプラグインでも動かせるEclipseくらいしか思いつきません。が、Eclipseの成長速度が保ちつづけられているのも、OSGiによるBundle分割、つまりモジュール化によるのです。
これは、Bundleの実装を変更しても、影響があるのは、そのBundleに依存しているBundleと、そのBundleのサービスを利用しているBundleだけだからです。一枚岩のアプリケーションのように、影響範囲が見えない、ということはかなり減らすことができます。
そして、モジュールの品質が高ければ、成長速度は落ちません。一つのBundleには一つのことをうまくやらせつづけてください。複雑なシステムは分離し、分離した対象にうまくやらせる原則は、Application -> Bundle -> Class -> Method、どれもかわりませんよね?
ということで、OSGiがもたらすインパクトや成長性について、なんとなく感じていただけたでしょうか?

デブサミ2010 OSGiセッションのお誘い

OSGiについて、デブサミ2010にて話をさせていただくことになりました。この日記を読んでいただき、興味を持っていただけた方は、ぜひご参加ください。この日記は、まだまだ整理しきれていない荒削りなバージョンです。当日までにガリガリ整理持っていきます。

*1:アプリケーションサーバを書いたことは無いので、詳しくは分かりません。

*2:JAR単位でパッケージングし、Bundleごとの依存関係はJARのメタ情報ファイルであるMETA-INF/MANIFEST.MFに記述し、Bundleごとにクラスローダを用意する、という構造

*3:素のMaven等はXML等に依存性を記述しますが、コマンドラインで追加するインタフェースはなかったと思います。後は、JARの検索ができれば最高なんですが。Spring Enterprise Bundle Repositoryでは検索できますが、コマンドラインでできてなんぼじゃないでしょうか?

*4:切り替えを考えたくなるのは、コマンドの違いや、クラスローダ方式の違いなど、仕様以外のところで検討することはあるでしょう。

*5:そのほか、EclipseベースのSpring Tool Suiteもあります。m2eclipseとbundlorをベースとした開発ができます。