第5回 マルチプロジェクトを使う[1]
今回の内容
前回はMaven-1.0のリリースを記念して、番外としてMaven-1.0情報をお届けしました。今回から2回に分けて、Mavenの持つ機能でも実践的な部類に入るマルチプロジェクトについての解説をします。まず第1回目の本稿では、プロジェクト規模が大きくなる際の弊害について、プロジェクトの構造、粒度、依存性に着目した分割指針、Mavenのマルチプロジェクトを使うメリットについて解説します。
複数のプロジェクトを透過的に扱うマルチプロジェクト
マルチプロジェクトとは、Mavenizeされた [1] 複数のプロジェクトを1つのプロジェクトのように扱い、コンパイル、リリース、インストール、レポーティングなどの各種ゴールを実行するための機能です。Mavenのほとんどの機能と同様に、マルチプロジェクトもプラグインによって実現されています。もちろん、標準組込のプラグインなので、Mavenをインストールしておけばすぐに使用できます。
Mavenは1つの巨大なソースツリーから成るプロジェクトではなく、小さなプロジェクトを複数組み合わせて1つの大きなソフトウェアを構成するというポリシーを貫いています。以降では、巨大プロジェクトの問題点と、マルチプロジェクトを使う理由について解説します。
巨大プロジェクトの問題点
対象が巨大で複雑になればなるほど、ソフトウェアの開発はサブシステムという単位で分割されて進められます。その場合には個々のサブシステムは独立して別々に開発され、成果物レベルで結合されます。その場合のサブシステムは、大きな機能で分割された粗い粒度になっています。また、サブシステムとして切り出しはしたが、分割するほどでもない(と思われていた)規模のソフトウェアの場合には、1つのプロジェクトの中にサブシステムにあたるソースの全てを共存させています。
プロジェクトは規模が大きくなればなるほど、ライブラリ、またはパッケージ間に様々な依存関係が発生します。同じソースツリーで開発している場合には、よほど注意しなければ循環依存に陥ってしまいます。循環依存とは、クラスの依存関係が双方向に存在するだけでなく、文字通り依存関係が循環してしまうような関係です。循環の輪に含まれているクラスが修正されると、関連するもの全てが影響を受けてしまう、変更に非常に弱い構造の典型例です。
このような問題を早期に発見するために、Mavenには依存関係をチェックする JDepend [2] のようなプラグインも存在します。循環依存したソースコードは、複雑に絡みあったまさにスパゲティ状態になり、修正コストも馬鹿になりません。
1プロジェクト1成果物
Mavenはポリシーとして「1プロジェクト1成果物」を掲げています。 [3] このポリシーに則って、Mavenのゴールはプロジェクトが作成する成果物 [4] を1つと定めています。もちろんMavenはAntのタスクを使用してゴールを記述できるため、やろうと思えば複数の成果物を生成することもできます。しかしそのようにMavenを使用してしまうと、逆にMavenを使用している意義が薄れてしまいます。
なぜMavenは1つの成果物しか許していないのでしょうか?それはプロジェクトの構造、粒度、依存性などと深い関係があります。
プロジェクトの構造
プロジェクトの規模が大きくなれば大きくなるほど、多くのモノが含まれ、構造が複雑化してしまいます。構造が複雑化してしまうと、全てを把握し、コントロールするのは至難の技になります。例えば、ソースツリーが何階層にも及び、数十のパッケージが混在してしまうと、個々のパッケージ毎の役割、分割指針、内容を把握するのが困難になります。こうなると、樹海に迷いこんだ旅人になってしまいます。恐らく、「樹海の歩き方」的なドキュメントを作成することになるのでしょうが、複雑なものを説明するために説明文書を用意することは、単純にする労力を怠っていることと同義です。
複雑なソースツリーを単純にビルドすることを考えても、依存するライブラリや、成果物毎のビルドプロセスの違いや環境などにより異なります。ソフトウェアの設計でも同じですが、複雑なものは単純なモノの組合せにすべきなのです。プロジェクトの単位を1つの成果物と定めることで、個々のプロジェクトの規模は極端に大きくなることはありません。 [5]
プロジェクトの粒度
ソフトウェアにおける粒度について、「モデルの粒度」という観点で論じる機会は多いです。しかしプロジェクトや成果物についての粒度を論じる機会はそれほど多くありません。粒度の視点でプロジェクトを眺めた場合、外部から見た「高凝集、疎結合」を適用することができます。 [6] プロジェクトに含まれるソースコードを高い凝集度にまとめ、外部と疎結合を維持することで、適切な粒度を実現することができます。高い凝集度の成果物を複数組み合わせることにより、疎結合な集合で全体のソフトウェアを構成することができます。こう考えると、Mavenでのプロジェクトの概念は、プロジェクトというよりも、むしろコンポーネントの概念に近いと言えます。
「高凝集、疎結合」を維持しつつ、プロジェクトをサブシステムのような粒度ではなく、サブシステムに含まれるコンポーネント単位に分割することで、見通しやすさが飛躍的に向上します。
プロジェクトの依存性
先にも説明しましたが、放っておくと、プロジェクト内、またはプロジェクト間の構造が循環依存を起しがちです。なぜ循環依存が起ってしまうのでしょうか?その原因の1つが 「 ソースコードが編集できてしまう 」という点です。Eclipseなどでは、プロジェクト間の依存関係も設定することができます。そういった場合だと、ソースコードのリンク機能を用いて、別のライブラリのソースなどに簡単にアクセスし、編集することができてしまいます。もちろん、こういった機能は非常に重要なのですが、循環依存に陥りやすいことも事実です。依存関係をソースではなく、成果物で関連することで、比較的循環依存を回避することができます。 また、「高凝集、疎結合」を実現すると独立性が高まり、循環依存も結果として防ぐことができます。
複数プロジェクトの制御に向かないAnt
実際に1つのソフトウェアプロジェクトが複数のプロジェクトから構成される場合には、全体をとりまとめるためにどのような機能が必要でしょうか? ここではAntを使用した例を見ていきます。
Antで階層構造になっているサブプロジェクトを扱う場合には、上位階層からサブプロジェクトのビルドファイルを使用してAntを実行します。上位階層からサブプロジェクトのターゲットを実行する場合には、どのターゲットを実行するかまで指定しなければなりません。
新しくサブプロジェクトを追加した場合を考えてみます。上位階層からはサブプロジェクトのビルドファイルを指定して、実行するターゲットを指定します。例えばSunが提供しているPetstoreサンプル [7] の src/componentsディレクトリを見てみましょう。このディレクトリでは、サブディレクトリ全ての core というターゲット名を指定して、上位階層から実行しています。このような場合だと、新しくサブプロジェクトを追加した場合、または削除した場合などにビルドファイルの変更が必要になってしまいます。ソースコードではないですが「変更の波及」は防がねばなりません。
また、例では全てのサブプロジェクトのcoreターゲットを、実行するという内容ですが、同様にclean、deployなど全てのサブプロジェクトを制御するターゲットをその数だけ記述するのも不便です。「変更の波及」が生じた場合に、全てのターゲットの内容を変更しなければなりません。正直筆者は勘弁願います。
Petstoreのbuild.xml抜粋:
<target name="core"> <!-- こんな記述がターゲット×サブプロジェクト分必要! --> <ant dir="xmldocuments/src" target="core"/> <ant dir="servicelocator/src" target="core"/> <ant dir="util/tracer/src" target="core"/> <ant dir="creditcard/src" target="core"/> <ant dir="address/src" target="core"/> <ant dir="contactinfo/src" target="core"/> <ant dir="customer/src" target="core"/> <ant dir="encodingfilter/src" target="core"/> <ant dir="lineitem/src" target="core"/> <ant dir="asyncsender/src" target="core"/> <ant dir="mailer/src" target="core"/> <ant dir="cart/src" target="core"/> <ant dir="catalog/src" target="core"/> <ant dir="signon/src" target="core"/> <ant dir="purchaseorder/src" target="core"/> <ant dir="uidgen/src" target="core"/> <ant dir="supplierpo/src" target="core"/> </target>
複数プロジェクトを分割統治するマルチプロジェクト
そこでMavenのマルチプロジェクトの登場です。元々はMavenに備わっていた、外部プロジェクトを扱う reactor という機能を、プラグイン形式にして使いやすくしたものです。マルチプロジェクトを利用することで、複数のプロジェクトを分割統治するために便利な次の作業を実現できます。
全サブプロジェクトへのゴールの実行
Antのように特殊なターゲットを記述せずとも、上位階層から、サブプロジェクト全体のゴールを一度に実行することができます。マルチプロジェクトはプラグイン形式で組込まれているため、実行するゴールも「multiproject」ゴールになります。デフォルトでは、全サブプロジェクトのsiteゴールを実行するようになっています。例えば「multiproject:deploy」で全サブプロジェクトの配備を、「multiproject:goal」で任意のゴールを実行することができます。ただし、サブプロジェクトのタイプによって、成果物がjar/war/earかをサブプロジェクト毎に「maven.multiproject.type」を用いて指定しておく必要があります。 [8]
上位階層での設定が不要
multiprojectゴールを実行するのにあたり、上位階層からのサブプロジェクトに関しての設定、記述は不要です。つまりサブプロジェクトが増えたとしても、上位階層で設定を変える必要がないということです。 [9] また、プロパティファイルに設定を数カ所指定するだけで、実行するサブプロジェクトの対象を変更することも可能です。
対象のサブプロジェクトを指定する場合の設定:
maven.multiproject.includes = "address/project.xml,cart/project.xml"
依存関係の解決
Mavenは標準でプロジェクト毎にDependenciesを指定します。サブプロジェクトで共通の依存関係がある場合は、project.xmlの <extend> 要素を使用して、明示的に外部のproject.xmlの情報を継承することができます。個々の依存関係はサブプロジェクト毎にproject.xmlを用意して指定します。ただし依存関係はあくまでも成果物(artifact)ベースの指定のみが可能です。つまりサブプロジェクト同士が循環依存している場合にはビルド自体ができなくなる恐れがあります。逆に言えば、このような状態になるために循環依存を早期発見できる、という利点になるわけです。
まとめ
今回は複雑なプロジェクトにおける問題点と、プロジェクトの構造、粒度、依存性に着目した場合の適切な分割の指針について解説しました。また、マルチプロジェクトを使用した、プロジェクトの分割統治について解説しました。
ここまで読んで、「何かに似ている?」と気づいた方はいますか?「複雑なものを単純に」、「高凝集、疎結合」、「循環依存の回避」これらはソフトウェアそのものの設計思想に類似しています。また、「1プロジェクト1成果物」は「単一責務」という考えにも通じます。
普段から、ソフトウェアの内部の設計に注力していても、プロジェクト設計が破綻してしまっては、良い設計も効果を発揮することができません。アーキテクチャーのような高い視点でもなく、かといって、ソースコードレベルのような詳細な視点でもない、中間的な視点で俯瞰することで、プロジェクトとしての全体最適化を実現する点がポイントと言えます。
次回予告
次回は今回に引き続き、マルチプロジェクトについて解説します。今回はマルチプロジェクトの背景にある思想や、概要でしたが、次週は実際のアプリケーションを用いたマルチプロジェクトの実例を解説します。
文書情報
Author: | 懸田 剛(Takeshi Kakeda) |
---|---|
Contact: | t-kakeda at esm.co.jp |
[1] | : Maven化されたという意味で使用します |
[2] | : http://www.clarkware.com/software/JDepend.html |
[3] | : http://wiki.codehaus.org/maven/WhyYouCantCreateMultipleArtifactsInOneProject |
[4] | : ここではJar/War/Earなどのアーカイブを指します。 |
[5] | : 筆者の経験では、Eclipse-2.0の頃にクラス数が1000を越えるソースツリーを1プロジェクトとして扱うと、メモリ消費量が相当なものでした。最近の3.0だとマシかもしれませんが... |
[6] | : 循環依存の構造は「密結合」と言えます。 |
[7] | : http://java.sun.com/developer/releases/petstore/ |
[8] | : Mavenが成果物の生成にどのゴールを使用すればよいかの判断に必要です。デフォルトはjarになっています。 |
[9] | : もちろん自分で設定をカスタマイズすることもできます |