Hatena::ブログ(Diary)

達人プログラマーを目指して このページをアンテナに追加 RSSフィード

2011-11-13

日本における初の解説書であるJenkins実践入門を送っていただきました

先日、技術評論社の傳智之さん(@dentomo)より、Jenkins実践入門を献本していただきました。どうも、ありがとうございました。

既に、Jenkins(Hudson)については、開発プロセスを自動化する継続的インテグレーションに欠かせないツールとして、日本でも非常に人気高いツールとなっており、また、雑誌やインターネットの記事でも今まで時々特集が組まれてきたと思います。

特に、作成者の川口耕介さん(@kohsukekawa)が日本人であるということもあって、日本語へのローカライゼーションやコミュニティー活動(プラグイン開発、勉強会など)が他の国と比べても活発なように感じます。実際、今回監修を担当された川口さん自身がまえがきの冒頭で、

Jenkinsは、世界中で広く使われている継続的インテグレーションサーバです。拡張性が高く、日本語化が進んでおり、活発なコミュニティーもあるお陰で、日本でもJenkinsの認知度は高いと思っています。

と書かれているように、世界に比べても日本はJenkins先進国と言えるのではないかとさえ思えます。ただし、その直後に書かれているように

しかし、実際に現場に適用しようと思った時に、拡張性・自由度の高さがかえって仇になって、「どうしたらいいかわかならい」という声もよく耳にします。ちょうど「レゴブロックが箱いっぱいにあるけれども、どうやったら家を作れるかわからない」というような状態です。

という言葉が、現状の状態を非常によく表現していると感じました。先進的なツールに対する積極的なコミュニティー活動が行われている一方で、多くの現場ではなかなか思うように普及が進まないというギャップは実際に多くの方が感じているのではないでしょうか。そのような状況にあって、本書のような気軽に読めるJenkinsの入門書が出版されたことは、このギャップを急速に埋める意味でも非常に大きな意味を持っていると思います。

本書は12個の章に分けて実践的なJenkinsの活用方法が解説されていますが、大きく3つに分けると

  • JenkinsやCIのコンセプトに対する入門(1章〜3章)
  • 実際の開発プロジェクトで必要な実践的な解説(4章〜8章)
    • JUnitテストレポートの組み込み
    • テストカバレッジレポートの組み込み
    • 自動コードインスペクション
  • より高度な話題の紹介(9章〜12章)

のように大きく3つの部から構成されているように感じました。全体的に画面キャプチャを多用して、非常に丁寧に解説しており、本書を読み進めるためには高度なプログラミングスキルは一切必要ありません。したがって、特に前半部分は非プログラマーのマネージャーやリーダーの方が、組織に初めてJenkinsを導入する際にも大いに参考になると思われます。一方で、既にJenkinsを使い込んでいる人にとっても、特に後半に書かれている、分散ビルドの考え方やプラグイン開発の方法については、十分に読み応えのある内容になっているのではないかと感じました。

入門書としては必要十分な適切な範囲を扱っていると思いますが、さらに、個人的に本書の内容に補うところがあるとすれば、

  • Mavenプロジェクトなどのマルチプロジェクトの構成方法(外部ライブラリーの依存管理)
  • テスト環境、プロダクション環境などのビルド対象環境の使い分けテクニック
  • Groovyなどのスクリプトを使った運用管理の自動化
  • Cloudbeeなどのクラウド開発環境や仮想環境でのJenkinsの活用方法

などでしょうか。これらについては、さらに、続編が期待されるところではありますが、本書を読み終えた方であれば、ネット上の情報で十分に自力で調べられるようになるかもしれません。もちろん、実際にアジャイルな開発環境を構築するためには、本書で紹介されているCI環境に加えて、ソースコードの構成管理ツールや、バグトラッキングシステム(BTS)についても調査する必要があるでしょう。

さて、私自身は、継続的インテグレーションなどのプラクティスCruiseControlなどの別のツールを利用して以前から部分的に試みたことはありましたが、2008年の終わりごろから(Jenkinsの前身となる)Hudsonを使い始めました。20個くらいのサブプロジェクトからなる、それなりに大きな規模のプロジェクトのビルドHudsonを使って自動化し、毎週定期的にテスト環境にリリースするというような、比較的ペースの速いプロジェクトで使ったことがあります。

ビルドシステム構築スキルの重要性 - 達人プログラマーを目指して

この時は、まだこのような解説書もなく、いろいろと手さぐりな状況でしたが、直感的なユーザーインターフェースのおかげもあり、ボタン一つで複数の環境用のビルドを自動実行するというシステムを作り上げることができました。最初のリリース時にはビルドからテスト環境までリリースするのに多くの手動作業が入っていたため、半日くらい時間を要したのですが、毎週リリースごとに徐々に手順を改良し、最終的にはビルド自体は20分もかからないくらいまで効率化できました。当時、このような入門書があったらどんなにかよかったのにと思いますね。

なお、以前に

SIerにはコード記述の自動化からビルド・デリバリの自動化へのトレンドの変化を理解してほしい - 達人プログラマーを目指して

で、一般にSIerビルドを自動化していないというような誤解を与えかねない書き方をしてしまったのですが、なんと、本書の著者は全員NTTデータで研究をされている方々のようです。残念ながら、多くの現場では研究成果を浸透させるということが難しいという状況もあるのかもしれませんが、SIerでも先進的な取り組みをされているところがあるのですね。実際に、かなり前に以下のような記事を書いたことを思い出したのですが

もしSIerがまともなエンジニアリングの会社だったとしたらどんな仕事が考えられるか? - 達人プログラマーを目指して

本来SIerというのは情報システム構築の専門家集団であって、むしろ、率先的にJenkinsのような仕掛けを導入することを提案すべきだと思います。実際に、自分の今の会社もそうなのですが、新しい機能を頻繁にリリースするためには、こうしたビルドの自動化、デプロイのパイプライン構築が欠かせません。ただし、一般のユーザー企業でこうした専門家集団を育てて確保することは容易ではありません。ThoughtWorks社のようにSIerが率先してこうした部分の研究を行い、ユーザー企業に対して提案できるようになるというのはSIerが目指すべきあるべき方向の一つなのではないかと感じました。

2011-01-02

開発時ビルドのバージョン番号の付け方に対するMavenとIvyの思想の違いについて

Apache Ivyの紹介と基本的な使い方 - 達人プログラマーを目指してに関連して説明させていただきます。

Mavenに慣れている人がIvyを使うときに必ずつまづくポイントとして、開発中の中間ビルド時のバージョン番号の付け方に対する両者の思想の違いがあります。Mavenの場合は、規約により開発中のバージョンには「-SNAPSHOT」という接尾辞をつけ、次のリリースを行うまでは同じバージョン番号のまま既存のビルド結果を上書きしながら使い続けることが前提となっています。

一方、もともとIvyの思想では、バージョン番号はビルド内容と1対1に対応しているべきという考え方があります。ビルド結果の中身が違うのであれば、バージョン番号も異なるべきという考え方です。ですから、中間ビルド(Ivyの用語では結合ビルドと呼ばれている)のバージョン番号にタイムスタンプを付けるとか、インクリメンタルなビルド番号を付けるなどの設定が簡単にできるようになっています。実際、<ivy:buildnumber>タグを使うことで、次に来るべき番号を自動的に計算する機能があります。なお、バージョン番号の付け方の思想については、以下にベストプラクティスとして説明されています。

Best practices | Apache Ivy™ Documentation

マルチプロジェクトのアプリケーションで、この方式でバージョン番号をビルドごとに変更する場合に、ivy.xmlにて依存関係にある兄弟のプロジェクトのバージョン番号に固定の番号を記述してしまうと管理が大変になります。兄弟のモジュールのバージョンがすべて同一と言い切れるならプロパティーファイルなどでバージョンを一元管理しておき、${プロパティ名}の記法で参照すればよいですが、一般にはモジュールごとの番号がそろっている保障はありません。

Ivyではこの問題に対処するために「動的レビジョン番号」という機能があります。たとえば、以下のように依存している対象のバージョンを"latest.integration"のように指定することで、その時点で最新のビルドに依存していると解釈してくれます。

<dependency org="foo" name="bar" rev="latest.integration"/>

さらに、Ivy2.0からはrevision constraintという仕掛けが設けられています。上記のような動的レビジョンの記述をレポジトリー上に直接パブリッシュする代わりに、パブリッシュ時(厳密にはdeliver時)にivy.xmlの依存定義を以下のように自動変換するようになっています。

<dependency org="foo" name="bar" rev="その時点の実際の静的レビジョン" revConstraint="latest.integration" />

こうすることで、レポジトリー上にパブリッシュ済みのアーティファクトについてはレビジョン番号を固定してビルド時の再現性を高くすることができるようになっています。ただし、revConstraint属性にもともとの動的レビジョンの情報を残しておくことで、必要に応じて動的な解決も可能になります。revConstraintの方を使うには<ivy:resolve>タグを明示的に使って、resolveMode属性をdynamicに指定するか、ivysettings.xmlデフォルトのresolveModeをdynamicにします。(開発中は全プロジェクトのビルドの代わりにモジュール単位での差分ビルドを行うことが多いのでdynamicモードが重宝します。)

このように、Ivyではビルドごとにレビジョン番号を設定することが推奨されているのですが、Maven風のSNAPSHOT的なやり方をまねることもできます。このような場合にIvyでは忘れずにchanging revisionの設定を行います。特定のレビジョン番号のパターンをchanging revisionであると宣言することで、レポジトリーキャッシュ動作を抑制する必要があるのです。(デフォルトではIvyはビジョン番号が同一で中身が異なるということのない前提で、レポジトリーダウンロード結果をキャッシュしてしまうからです。これはトラブルの元になります。)このためには、ivysettings.xmlレポジトリーに対するレゾルバーを宣言する部分で以下のようにcheckModified属性とchangingPattern属性を合わせて指定します。

<filesystem name="local" checkmodified="true" changingPattern=".*SNAPSHOT">
  <ivy pattern="${ivy.local.default.root}/${ivy.local.default.ivy.pattern}" />
  <artifact pattern="${ivy.local.default.root}/${ivy.local.default.artifact.pattern}" />
</filesystem>

この点については以下が参考になります。

Main Concepts | Apache Ivy™ Documentation

Ivy is Useful

いずれにしてもかなり複雑ですね。この点はIvyで最初にはまるポイントの1つだと思います。私は最初はIvyのマニュアルのベストプラクティスに従って、ビルドごとに番号を振り出す方式を使っていたのですが、動的レビジョンの解決などかなり複雑ですし、レポジトリーのサイズがビルドごとに増大してディスク容量が足りなくなるなどの問題もありました。ただし、SNAPSHOT方式にすると以上に説明したようにIvy固有のキャッシュの問題でトラブルが発生したりします。なかなか悩ましいところです。

2011-01-01

Apache Ivyの紹介と基本的な使い方

Apache Ivyについては本ブログでも何回か用語自体は取り上げてきましたが、現状日本語での情報が限られるためか、AntそのものやMavenに比べるとユーザーが少ないように思われます。ここで基本的な使い方やMavenとの違いについて簡単に紹介させていただきたいと思います。

Apache Ivyとは

本家のホームページは以下の通りです。

Home | Apache Ivy ™

もともとはJayasoftという組織で開発されていたツールですが、バージョン2.0以降、Antの関連プロジェクトとしてApacheプロジェクトの元に加わっています。(Apacheというブランド名はツールを組織に導入する際に結構重要ですね。)

上記のホームページでは「アジャイルな依存性管理ツール」として紹介されていますが、Mavenの機能の中からビルド機能やプロジェクト管理機能を無くして、ライブラリーの依存関係の管理に特化したツールとなっています。「アジャイル」として紹介されている意味は、おそらく多くの機能を詰め込んでいるMavenと比較して、依存関係管理に特化していることにより、身軽であるということを強調しているのだと思います。しかし、実際に使いこんでみるとわかりますが、非常に高度なカスタマイズが可能な柔軟性も備えており、かなり奥の深いツールになっています。好みの分かれるところですが、確かに、多少押し付けがましい規約の多いMavenと比較して、本当に使いたい機能のみ利用して「The simplest thing that could possibly work = 要件を満たすもっとも単純なやり方」を実現するにはより適したツールであると言える思います。

基本的には以下のような場合に利用を検討するとよいと思います。

  • 長年Antを使っている組織で、ソースツリーのディレクトリー構造を変えられない場合
  • earファイルの生成など複雑なビルド対象を扱う場合
  • ビルドスクリプト専門の開発、保守担当をアサインできる場合

Antの専門家のいるプロジェクトで、かつ、Mavenに移行するのも大変という場合に重宝するツールです。有名なところだとSpring Framework(本体部分とWebFlowの部分)のビルドシステムがIvyを使って構築されています。また、Twitterのscalaを使ったプロジェクトのいくつかでIvyを使っている例があったと思います。

なお、MavenとIvyとの選択については、以下もご覧ください。(Ant + Ivy vs Maven

Ivyのインストール手順

1.JDKのインストール

AntやIvyを利用するためには、当然JDK(JREでなく)のインストールが必要です。

Oracle Technology Network for Java Developers | Oracle Technology Network | Oracle

インストール後環境変数JAVA_HOMEをインストール先のディレクトリーに指定します。

2.Antのインストール

Ivyを使うためには、Antのインストールが必要です。

Apache Ant - Binary Distributions

からBinary版をダウンロードし、適切なディレクトリーに展開します。環境変数ANT_HOMEを展開先ディレクトリに設定します。

3.Ivyのインストール

Download | Apache Ivy ™

からBinary版をダウンロードし、適切なディレクトリーに展開します。次に、展開したディレクトリーの直下に入っているivy-バージョン番号.jarをANT_HOME配下のlibサブディレクトリーにコピーします。

4.HTTPプロキシーの設定(オプショナル)

インターネットに対して直接接続できない環境では、

FAQ | Apache Ivy ™

を参考にしてANT_OPTS環境変数を設定してください。

5.PATH環境変数の修正

PATH環境変数の先頭に%JAVA_HOME%\bin;%ANT_HOME%\binを追加します。(もちろんWindowsの場合。LinuxやMacユーザーの方は常識ですよね。)

hello-ivyサンプルを実行してみる

まずはサンプルを実行する

Ivyのインストールができたら、インストールの確認も兼ねて、まずは付属しているhello-ivyサンプルを動作させてみます。

コマンドプロンプトを開き、

(Ivyインストール先)\src\example\hello-ivy

に移動します。そこでantと入力し、

Buildfile: D:\development\tools\Apache\apache-ivy-2.2.0\src\example\hello-ivy\build.xml

resolve:
[ivy:retrieve] :: Ivy 2.2.0 - 20100923230623 :: http://ant.apache.org/ivy/ ::
[ivy:retrieve] :: loading settings :: url = jar:file:/D:/development/tools/Apache/apache-ant-1.8.1/lib/ivy-2.2.0.jar!/org/apache/ivy/core/settings/ivysettings.xml
[ivy:retrieve] :: resolving dependencies :: org.apache#hello-ivy;working@Ruby
[ivy:retrieve] 	confs: [default]
[ivy:retrieve] 	found commons-lang#commons-lang;2.0 in public
[ivy:retrieve] 	found commons-cli#commons-cli;1.0 in public
[ivy:retrieve] 	found commons-logging#commons-logging;1.0 in public
[ivy:retrieve] :: resolution report :: resolve 190ms :: artifacts dl 10ms
[ivy:retrieve] 	:: evicted modules:
[ivy:retrieve] 	commons-lang#commons-lang;1.0 by [commons-lang#commons-lang;2.0] in [default]
	---------------------------------------------------------------------
	|                  |            modules            ||   artifacts   |
	|       conf       | number| search|dwnlded|evicted|| number|dwnlded|
	---------------------------------------------------------------------
	|      default     |   4   |   0   |   0   |   1   ||   7   |   0   |
	---------------------------------------------------------------------
[ivy:retrieve] :: retrieving :: org.apache#hello-ivy
[ivy:retrieve] 	confs: [default]
[ivy:retrieve] 	0 artifacts copied, 7 already retrieved (0kB/10ms)

run:
    [javac] D:\development\tools\Apache\apache-ivy-2.2.0\src\example\hello-ivy\build.xml:53: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
     [java] standard message : hello ivy !
     [java] capitalized by org.apache.commons.lang.WordUtils : Hello Ivy !

BUILD SUCCESSFUL
Total time: 1 second

のように最後に「BUILD SUCCESSFUL」と表示されたら成功です。

何が起こっているか

私も最初そうでしたが、ここまで無事動作したものの、実際に何が起こっているのか、サンプルの動作を理解する次のステップが結構大変なところですね。このサンプルのソースコードは、src\example\Hello.javaのただ1つのクラスだけですが、ソースを開いてみるとわかるように、このクラスはcommons-langとcommons-cliという2つのライブラリーに依存しています。したがって、このクラスを正しくコンパイルして実行するためには、これらのライブラリーのjarファイルが必要となります。

ここで依存性管理を使わずに、Antを使って普通にビルドを行う場合、通常はlibディレクトリーなどjarファイルの格納場所を用意しておき、Antのビルド時のクラスパスにそのjarファイルの場所を追加することになりますが、この考え方はIvyを使う場合もあまり変わりません。ただし、依存関係をビルドスクリプトに直接記述する代わりに、ivy.xmlに以下のように記述します。

<ivy-module version="2.0">
    <info organisation="org.apache" module="hello-ivy"/>
    <dependencies>
        <dependency org="commons-lang" name="commons-lang" rev="2.0"/>
        <dependency org="commons-cli" name="commons-cli" rev="1.0"/>
    </dependencies>
</ivy-module>

基本的には直感的に理解できる内容で、特に難しいことはありません。(Mavenのpomファイルに比べて簡潔でかなりスッキリとしています。)このファイルでは、まず<info>タグでこのモジュールのorganisation*1がorg.apacheで、module名がhello-ivyであることが宣言されています。organisationはMavenで言うところのgroup、moduleはartifactにそれぞれ対応する概念です。次に、<dependencies>タグの中身でこのモジュールが依存するライブラリーが宣言されています。<dependency>タグのorg(organisation)属性はMavenのpomファイルの<group>要素、name属性は<artifact>要素、rev属性は<version>要素にそれぞれ対応していると考えてください。

このように、モジュールの依存関係を宣言しておけば、後はAntビルドファイルの任意の場所で<ivy:retreave>タグを記述することで、任意のディレクトリー配下に自動的に依存する(推移的な関連も含めて)jarファイルをダウンロードすることができます。実際、このサンプルプログラムのbuild.xmlファイルでは、


    <target name="resolve" description="--> retreive dependencies with ivy">
        <ivy:retrieve/>
    </target>    
    
 ...

    <target name="run" depends="resolve" description="--> compile and run the project">
        <mkdir dir="${build.dir}" />
        <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="lib.path.id" />
    	<property name="msg" value="hello ivy !"/>
        <java classpathref="run.path.id" classname="example.Hello">
        	<arg value="-message"/>
        	<arg value="${msg}"/>
    	</java>
    </target>

のように記述されているため、runタスクが実行される前に、resolveタスクが実行され、この中で<ivy:retrieve/>が実行されています。デフォルトではlibサブディレクトリーにライブラリーが取得されますが、

    <property name="lib.dir" value="lib" />
    <property name="build.dir" value="build" />
    <property name="src.dir" value="src" />

    <path id="lib.path.id">
        <fileset dir="${lib.dir}" />
	</path>
    <path id="run.path.id">
        <path refid="lib.path.id" />
        <path location="${build.dir}" />
    </path>

の部分でクラスパスが設定されているため、コンパイルや実行が正しく行われます。

結局、ポイントはivy.xmlモジュールの定義と依存関係の定義を行い、ライブラリーを<ivy:retrieve/>で取得するというだけです。Ivyが良いのは第一にこの気軽さであり、これなら既存のAntビルドにも自由に組み込むことが可能なのがお分かりいただけると思います。

Ivyの基本概念について

レポジトリーキャッシュ

このように、通常は<ivy:retrieve/>を用いてローカルのディレクトリーにjarファイルをダウンロードしてくることが普通ですが*2、最終的にはMavenと同様に、jarファイルをレビジョンごとに管理したレポジトリーから取得するようになっています。Ivyのレポジトリーを読み込むモジュールレゾルバーと呼ばれていますが、これがちょうどStrategyパターンになっていて、さまざまな実装をプラグインできるようになっています。ファイルシステム上で管理する実装や、HTTPサーバー上で管理する実装などさまざまなものがあり、また、ディレクトリー構造も細かくカスタマイズができるようになっています。*3

resolvers | Apache Ivy™ Documentation

そして、実際にはデフォルト設定でMavenのセントラルレポジトリーを読み込むレゾルバーが登録されています。(Maven形式のレポジトリーを読み込めるStrategy実装が利用されている)さらに、ここが少し分かりにくいところですが、レポジトリーを何段にも自由に階層化することが可能になっています。よくあるパターンは

のように段階的にレポジトリーを構成しておき、順番に解決させるといった設定が可能です。

なお、Ivyではリポジトリーとは別にキャッシュという概念が存在します。デフォルトではホームフォルダー配下の.ivy2ディレクトリー配下にキャッシュが作成されます。hello-ivyを実行した段階では、以下のようなキャッシュディレクトリーが生成されているはずです。

f:id:ryoasai:20110101231247p:image

Mavenの場合は共有されるレポジトリーとローカルのレポジトリーが存在し、ローカルのレポジトリーキャッシュの役割を兼ねていますが、Ivyの場合キャッシュ専門のディレクトリーが存在する点に注意してください。

ivysettings.xmlを使ったIvyの基本設定のカスタマイズ

デフォルトではIvyのjarファイルの中で定義されている設定ファイルが利用されますが、通常ivysettings.xmlと呼ばれる名前のファイルを作成することで、Ivyの動作をカスタマイズすることができます。たとえば、レポジトリーの階層構造やキャッシュの場所などを設定できます。ivysettings.xmlのリファレンスは以下にあります。

Settings Files | Apache Ivy™ Documentation

ivysettings.xmlを使った設定の簡単な例を試すには、exampleの中で、とりあえずchained-resolversを動かしてみるのが良いと思います。

src\example\chained-resolvers\chainedresolvers-project

サブディレクトリーにcdして、antコマンドを起動してください。hello-ivyの場合と違って、build.xmlファイルの中で以下のように<ivy:settings>タグが記述されています。

    <ivy:settings file="${ivy.settings.dir}/ivysettings.xml" />

これによって..\settings\ivysettings.xmlファイルが初期化時に読み込まれます。

そして、以下はivysettings.xmlの中身です。

<ivysettings>
  <settings defaultResolver="chain-example"/>
  <resolvers>
    <chain name="chain-example">
      <filesystem name="libraries">
        <artifact pattern="${ivy.settings.dir}/repository/[artifact]-[revision].[ext]" />
      </filesystem>
      <ibiblio name="ibiblio" m2compatible="true" />
    </chain>
  </resolvers>
</ivysettings>

ここでは、<resolvers>タグの中身で

  • ローカルのファイルシステム上のレポジトリーを読み込むためのfilesystemレゾルバーをlibrariesという名前で登録
  • Mavenのセントラルレポジトリーを読み込むibiblioレゾルバーをibiblioという名前で登録
  • これらを順に解決するためのchainレゾルバーをchain-exampleという名前で登録

というようにレポジトリーに対するレゾルバーが設定されています。そして、<settings>タグでchain-exampleをデフォルトレゾルバーとして登録しています。以下のように、ビルド時のログを見ると、test.jarがローカルファイルシステム上のlibrariesから、commons-langがセントラルレポジトリーから読み込まれたことが分かります。(ただし、2回目以降のビルドではキャッシュが利くため、セントラルレポジトリーからのダウンロードは行われません。)

[ivy:retrieve]  found commons-lang#commons-lang;2.0 in ibiblio
[ivy:retrieve]  found org.apache#test;1.0 in libraries
Ivyの依存管理の中心的機能であるコンフィグレーション

前節のsettingsという概念と非常に混同しがち*4なので注意が必要なのですが、Ivyにはコンフィグレーション(configuration)と呼ばれる別の概念があり、Ivyの柔軟な依存管理を行う上での中心的な機能となっています。これは、Mavenで言うところのスコープ(scope)の概念に近いものですが、遥かに柔軟です。Mavenの場合、最初からtest、runtime、compile、providedなどの依存性のスコープが決められています。たとえば、testスコープは単体試験の時に利用されるライブラリーで、compileは本体のソースコードのコンパイルに利用するライブラリーといった具合です。また、これらのスコープにより推移的依存関係の際の動作も決まります。

一方、Ivyのコンフィグレーションは各モジュールのivy.xmlファイルで<configuration>タグを記述することにより、任意の項目を追加できるようになっています。通常のruntimeやtestといったスコープに加えてweblogic、jbossなど特定のAPサーバー固有の依存関係を定義したり、integration-testのように結合試験の時のみ必要な依存関係を定義したりできます。この概念は最初はちょっと分かりにくいですが、まずはサンプルを動かしてみてください。

example\configurations\jdbc-example\ivy.xmlは以下のように記述されています。

<ivy-module version="1.0">
    <info organisation="org.apache" module="configurations" >
    	<description>
    		This is an example project that aims to demonstrate the usage of the configuration in ivy.
    		This project provide 4 configurations. Each configurations describe the requirement to build or run the project
    	</description>
    </info>
    <configurations>
    	<conf name="compile" description="This is this configuration that describes modules need to build our project"/>
    	<conf name="test" extends="compile" description="This is this configuration that describes modules need to run test on our project"/>
    	<conf name="rundev" extends="compile" description="This is this configuration that describes modules need to execute our project in a dev environement"/>
    	<conf name="runprod"  extends="compile" description="This is this configuration that describes modules need to execute our project in a production environement"/>    	
    </configurations>
    
    <dependencies>
	
        <dependency org="commons-cli" name="commons-cli" rev="1.0" />
        
        <dependency org="mckoi" name="mckoi" rev="1.0.2"  conf="rundev->default"/> 
        
        <dependency org="mm-mysql" name="mm-mysql" rev="2.0.7" conf="runprod->default"/> 
                
        <dependency org="junit" name="junit" rev="3.8" conf="test->default"/> 
    </dependencies>
</ivy-module>

今までのサンプルと違い、<configurations>が記述されており、この中で複数の<conf>タグによって複数のコンフィグレーションが定義されています。実際ここではcompile、testという標準的なコンフィグレーションに加えてrundevとrunprodというコンフィグレーションが定義されている点に注目してください。

さらに、<dependency>タグの中ではconf属性が記述されていることに注意してください。たとえば、

conf="runprod->default"

という記述はそのライブラリーのrunprodコンフィグレーションにおいて、依存相手のdefaultコンフィグレーションに依存することを示します。慣れないと非常に難しく思われるかも知れませんが、要するに依存関係といっても、目的によってさまざまなものがあるわけですから、その依存関係の種類ごとにコンフィグレーションとして定義しておき、おのおの必要な依存関係を自由に定義できるということに過ぎません。

このようにivy.xmlでコンフィグレーションを定義したら、あとはビルドスクリプト中で<ivy:retrieve>タグを記述する際に、どのコンフィグレーションを使うかをconf属性で指定するだけで、目的に応じたjarファイルのみを取得してくることができるわけです。(conf属性を指定しない場合conf="*"と見なされ、全コンフィグレーションの依存関係が取得されます。)

このコンフィグレーション別のretrieve機能を使えば、対象APサーバーごとに別々のコンフィグレーションを定義しておき、warファイルに可能するjarファイルを区別するなどといったことが簡単に行えるようになります。

なお、ivy:retrieveのpattern属性で以下のような書き方をすることで、jarファイルの取得先をコンフィグレーション名ごとのサブディレクトリーに分けることも可能です。

<ivy:retrieve pattern="${ivy.lib.dir}/[conf]/[artifact]-[revision].[ext]"/>
ビルド成果物のパブリッシュ

通常、ビルドした成果物(jarファイルなど)は最終的にパブリッシュという操作を行って、レポジトリーに対してメタ情報(ivy.xml)と共に公開します。パブリッシュを行う対象のモジュールではivy.xmlにて<publications>タグを記述します。(Ivyのプロジェクトでは一つのモジュールで複数のartifactをパブリッシュすることもできます。)

    <publications>
      <artifact name="sizewhere" type="jar" conf="core" />
    </publications>

そして、build.xmlにて<ivy:pubilsh>タグを記述することで、実際にレポジトリーに対してパブリッシュ操作を実行します。詳しくはマニュアルを参照してください。

マルチプロジェクトのビルド

Ivyのような依存性管理が本当に重要になってくるのは複数のプロジェクトから構成されるような大規模なプロジェクトにおいてです。このようなマルチプロジェクトの一括ビルドを行う際にもIvyは便利です。サンプルとしては、

src\example\multi-project

が参考になると思います。ここではポイントだけ説明すると、このサンプルのルートディレクトリーに格納されているbuild.xmlにて

  <target name="buildlist" depends="load-ivy"> 
    <ivy:buildlist reference="build-path">
      <fileset dir="projects" includes="**/build.xml"/>
    </ivy:buildlist>
  </target>
  
  <target name="publish-all" depends="buildlist" 
  			description="compile, jar and publish all projects in the right order">
    <subant target="publish" buildpathref="build-path" />
  </target>

のように記述されている部分で、<ivy:buildlist>タグを使って配下のすべてのbuild.xmlからビルドリストを作成します。ここでIvyが自動的に依存関係から適切な順番でリストを作成してくれるところがポイントですね。(Ivyを使わない場合は手動でリストを記述する必要がある)あとは通常のAntでマルチプロジェクトのビルドを行う場合と同様、 <subant>タスクを記述してリストの順番にサブプロジェクトのタスクを自動的に呼び出させます。

まとめ

Ivyの基本的な使い方の要点をまとめると以下の通りとなります。

  • モジュール(プロジェクト)ごとにivy.xmlを記述して、依存関係、コンフィグレーション、パブリッシュ対象成果物を定義する。
  • Ivyのグローバルな設定はivysettings.xmlに定義し、<ivy:settings>タグで読み込む。
  • コンフィグレーションとは独自の依存関係のスコープを定義したものである。
  • レポジトリーとはjarファイルなどのライブラリーをバージョンごとに管理したものである。
  • レポジトリーから読み込むためのレゾルバーの階層を自由に定義することができる。
  • レポジトリーから読み込んだデータは最終的にローカルのファイルシステム上にキャッシュされる。
  • ビルドスクリプトの任意の場所で<ivy:retrieve>タグを記述することでjarファイルなどをレポジトリーから取得することができる。
  • ビルドした成果物(アーティファクト)は<ivy:publish>を実行することでレポジトリーにメタ情報と共にパブリッシュ(公開)される。

ここで紹介した内容は本当に基礎の部分のみです。詳しくは以下のリファレンスを参照してください。

Reference | Apache Ivy™ Documentation

なお、IvyDEというEclipseのプラグインもあります。

Home | Apache IvyDE™

以前のバージョンは結構不具合が多かったのですが、最近は比較的安定して使えるツールになってきているようです。

*1:新しいバージョンではorganizationでも可。Ivyの開発者はフランス人らしいので、もともとはイギリス英語になっている。

*2:<ivy:cachepath>タグを利用するとダウンロードなしでキャッシュ上のjarファイルをクラスパスに追加することもできる。

*3:基本的にIvyの思想はこのStrategyパターンによるさまざまな実装方式のプラグインにあるため、この点を理解することが非常に大切です。Strategy大好きのSpringがIvyを利用しているのもちょっと納得できることです。

*4:共に翻訳すると構成とか設定という意味になる。

2010-12-25

もしSIerがまともなエンジニアリングの会社だったとしたらどんな仕事が考えられるか?

以前にも何度か書いたように私自身一応SIerと呼ばれる会社で(肩書き上SEとして)働いているのですが、このブログでSIerのことについて書くと、おそらく技術力のある優秀なPGの方からだと思うのですが、

なぜみんなSI業界から飛び出さないんでしょうね

真っ当なプログラマーを目指すのならSI屋には就職しちゃダメ

のようなコメントをいただくことが多いですね。本当にPGが技術力を発揮しようと考えたら、SIerやその下請け専門の会社ではなくて、最新のWebサービスをやっている会社とか、ネットゲームの会社とか優秀なPGにとってもっと働き甲斐のある魅力的な分野が他にたくさんあるということでしょうか。極端な話、外資系の銀行とか、海外で仕事をすべきという意見すら時々聞きます。

ただし、(私自身そういった分野で活躍しようと考えるほど最新の高度な技術力を持っているという自信がないというのもありますが)、私がもともとSI業界で働こうと思ったきっかけは、基幹業務を支える業務システムの開発というものに最大のやりがいを感じたというところにありますね。

実際、理論が整然と確立されており、仕様が明確に記述できるコンパイラーの開発とは違って、業務システムの要件はもともと無駄が多かったり、矛盾だらけだったり、重複が多かったりするわけです。しかも、要件はどんどん変更されたり、追加されたりします。そういう制約が多くぐちゃくちゃな世界から綺麗な構造を見つけ出して、機能拡張や変更に耐えられるアーキテクチャーを考え出していくというところに私は業務システム開発の本来の面白さがあるはずだと期待しました。業務システムは社会を支えるインフラを構築するという社会的な責任の面でも重要だし、プログラミング技術としても本来非常にチャレンジングで面白みのある分野であると信じていました。このブログでも紹介したファウラーなんかの本を読むとドメインモデリングやリファクタリングなどの設計技法を駆使して複雑な業務要件を綺麗に解きほぐして無駄のない設計にしていくというようなことが書かれていますが、そういった手法によって、システム開発のやり方をもっとスマートに効率的に改善することができるに違いないと素直に信じてしまったということがあります。(私も若かった、笑。)

残念ながら、私のそうした期待とは違い、多くの場合企業の業務システム開発を担当する中心的な存在である大手SIerは大きなプロジェクトを請けて下請けに丸投げするITゼネコンと化してしまっています。*1だから、大手SIer側は技術の会社ではなくて、単なる人集めと営業のビジネスとなり、下請けの中小SIer、ソフトハウスの側は付加価値の低い単純労働作業となってしまっているという現実があります。Java EEや.NETのようなオブジェクト指向のプラットフォームやESBといった技術も既に何度も指摘してきたように現状あまり有効に活用されているとは言えないようです。

ところで、実は「SIerとは何か」という点を考えると、そもそも非常に分かりにくいところがあります。システムインテグレーター - Wikipediaだと、

システムインテグレーター(英語:System Integrator)は、個別のサブシステムを集めて1つにまとめ上げ、それぞれの機能が正しく働くように完成させる「システムインテグレーション」を行なう企業のことである。

日本の情報システムにおけるシステムインテグレーターとは、情報システムの開発において、コンサルティングから設計、開発、運用・保守・管理までを一括請負する情報通信企業である。SIer(エスアイアー)とも呼ばれる。

と定義していますね。もともとの英単語の意味からすると前半の定義の通り、EAIやSOAなどの技術を使ってシステムを統合するのが仕事のように思われますが、実際はシステム統合案件ばかりやっているわけではなくて、業務システムを一括請負して開発したり、保守、運用、管理までやっている会社ということになるようです。歴史的な理由や雇用の問題からゼネコン化しているという実態はありますが、本来SIerという言葉自体にはエンジニアリングと対立するもの、PGと敵対するもの(?)という意味は含まれていないようです。

私は、SIerが初心に戻って本来のSIとな何かという点を考えなおすことで現状の請負中心のビジネス構造をある程度保ちながらも、本来あるべきエンジニアリングとしての仕事ができる道がないわけではないと思います。いくつかアイデアをあげると

  • SIerは業務フローやユースケース分析などシステムの純粋にwhatの部分のみ考え、画面定義や実装などhowの部分は実装者の自由に任せる。(howとwhatの分離)
  • SIerはシステム固有の業務知識が必要なドメイン層を実装まで含めて担当し、画面開発や帳票など業務知識があまり要らないところを専門の業者に外注する。(コアドメインと周辺機能との分離)
  • SIerはシステムの全体的なベースラインアーキテクチャ(もちろんモデル図だけでなくてソースコードも作成する前提。)やコンポーネントのインターフェース、SLA上の規約のみを決め、コンポーネントの実装を外注する場合中身の実装には口を出さない。実装の詳細や設計は下請けに自由に考えさせる。(インターフェースと実装の境界で役割を分担する)
  • SIerは名前のとおり、コンポーネントやサービスの統合案件を中心に仕事をする。(モノリシックな大規模アプリケーションの開発は避ける。)
  • SIerはユーザーの代理として最終的な機能試験、システム試験、性能試験を行う役割を担う。これらの試験のエキスパートを育成する。(試験タスクの体系化、知識集約化)
  • SIerは生産性の高い開発環境一式(ビルドシステムなどを含む)を構築、標準化する役割を担う*2(開発生産ライン構築)

この場合、最初の案はコンサルファームのように純粋に上流工程に特化した仕事となりますが、それ以外はPGまで含めて上流から下流まで担当する要素がある点に注意してください。少なくとも現状は建設業や製造業の発想で工程で上流、下流と分け商流の構造もその工程に対応したものとなっています。しかし、それ以外のオブジェクト指向、サービス指向、クラウドにより適合した方法で分離する方法もあってしかるべきと考えます。

ビジネスモデルとしては収益構造など難しい問題があるため簡単ではないはずですが、少なくとも現状のように工程で分離して下請けに丸投げする方法よりエンジニアリングの観点からもっと賢い方法があるのではと思います。そうすることでPG(この場合本来のSEを含めてもよい)がSIerでもっと活躍できる場所が増えるのではないでしょうか。しかも、顧客や下請けからもっと尊敬される会社になることができるのではないでしょうか。

※なお、SIerにはハードウェアなどシステム基盤や運用の仕事もありますが、ここではPGとして興味のある開発の部分に絞って考えました。

*1:こうなってしまった背景にはさまざまな合理的な理由が考えられるようですが。指定されたページがみつかりませんでした - goo ブログ

*2:最近は仮想技術を使って開発環境一式をオンデマンドで配置するといった技術もあるようです。

2010-11-30

Java EEのearファイル形式はMavenの天敵

Ant + Ivy vs Maven - 達人プログラマーを目指して

で、AntとMavenの比較を行いました。そこではビルド要件の複雑度からAntが有利な場合とMavenが有利な場合について分析しています。それに関連して、最近Maven地獄に陥っているプロジェクトに関わることになり、pomのリファクタリングを行っているのですが、その作業をしていてひとつ気づいた点としてJava EEのearファイル形式はMavenの天敵であるという点があったので紹介しておきます。

earファイル形式は、Java EEの最初のころからあった形式ですし、Mavenでもしっかりとサポートしてくれて当然という期待があるのですが、意外にも現状サポートが不十分で、いろいろと問題があるということがわかりました。

ear内のモジュール間でのjarファイルの共有が困難

まず、第一にear内に複数のwarやrarなどのモジュールが含まれているようなケースで、jarファイルの共通化が極めて困難なことがあります。ear内でのjarファイルの共通化については、weblogicのAPP-INFディレクトリーやManifestクラスパスを使った方法など、以前から可搬性の少ない方法が使えたのですが、Java EE5からはear内の任意のディレクトリーをライブラリー用のjar格納ディレクトリーとして定義することが可能です。ところが、Mavenで何も考えずにmaven-ear-pluginでearファイルを作っても、そのようなjarファイルの共通化は当然されないため、同じjarファイルが複数のモジュール内に格納されてしまい、大変巨大なearが出来上がってしまいます。特に、warファイルに関してはSkinny war作成の問題として知られており、昨年にリリースされたmaven-war-pluginの2.1以降で、一応対応方法が追加されています。

Apache Maven WAR Plugin – Creating Skinny WARs

しかし、上記の対応をしても、m2eclipseのバグでeclipse内で正しくクラスパスが設定されないという問題があり、結局不本意ながら*1<scope>provided</scope>な依存関係を追加しまくるという対処が必要になります。また、そもそも、rarファイルではそのようなサポートすらされていないようなので、やはり、<scope>provided</scope>な依存関係を追加する必要があります。

MavenのSkinny war問題については、以下に詳しくまとめられています。

no title

また、以下の記事のように最近でもこの問題に対してさまざまな試みがあるようですが、どれもいまひとつな感じです。

DRY and Skinny War - DZone Java

eclipseのWTPとの相性が非常に悪い

m2eclipseとearファイルとの相性の問題が未だに解決されていないようであり、eclipse上からデプロイをかけると、期待されるファイルが正しく生成されず、デプロイできません。結局、外部でmvn packageなどを呼び出してearファイルを作成してからデプロイするという処置が必要になるようです。結果としてホットデプロイのようなことも困難になります。

Log in - Sonatype JIRA

Log in - Sonatype JIRA

NetBeansの場合はどうなのか試していませんが、少なくともeclipse上でearを使うようなプロジェクトでMaven化を行うと開発効率が大きく低下する可能性があるため注意が必要だと思います。もちろん、「earなんて複雑な形式は使わないよ」という幸せなプロジェクトでは問題ないのですが、EJBの採用が決定していたり、rarを使わないといけないプロジェクトなどでearを使わなくてはならないような場合は、それでもMavenを使うべきかどうか検討した方がよいでしょう。この場合プロジェクトにAnt使いがいるのならAntを使った方が無難かもしれません。*2

*1:これは本来のprovidedの用法ではないはずであくまでもワークアラウンドの色が強い。

*2:Springのビルドシステムがそうしているように、Antを使う場合でもディレクトリー構造をMavenの規約にそろえておくのはプラクティスとしてはよい習慣だと思います。