google guiceはDIに適したフレームワークか?
リリースされた当初はそれなりに話題になったような気がしますが、最近あまり話題を聞かないような気がします。特に普及が進んでいるという話も寡聞にして聞きません。
何故でしょう。
この辺だと思いますが、問題はここだと思います。
- DIに特化していてシンプル
これは、guiceの良さというかウリでもあると思いますが、DIに特化しているということは即ち、DIだけしかできないということです*1。しかし、世の中に
DIだけしたいなんて人はいるのでしょうか?
滅多にいないと思います。絶対いないとまではいいませんが。大抵の人は、例えば、Webアプリケーションを作るなどの何か他の目的を達成するための手段の一部としてDIしたいのであって、DIするためにDIしたところであまり意味はありません。
即ち、DIとはそもそもが手段の一部であり、それ自身が目的にはなり得ない、そもそも他の手段と組み合わせて使うべきものなので、「何とどういう風に組み合わせて使うか」が問題なのではないかと思うのですが、DIに特化しているということは即ち、
何とどういう風に組み合わせてどう使うかは好きにしろ
ということになると思います。これがspringとかseasarだと、
山ほど組み合わせ方のベストプラクティスが出てきてこれからDIを勉強する人にも活用しやすいと思うのですが。
即ち
guiceは、DIそのものはシンプルで使いこなすための敷居は低いのかもしれませんが、肝心の何とどういう風に組み合わせて活用すればいいかは
のが、普及が進まない原因のような気がします。即ち、DIに適したフレームワークであるとは言い難いのかも知れません。
まぁようするに何が言いたいのかというと
guiceをベースとして、何とどういう風に組み合わせればいいかを盛り込んだ、より上位のフレームワークでも登場しない限りは一般に普及しないんじゃないかと思う訳で、私もかつて二度ほど趣味ベースで勉強も兼ねてその「より上位のフレームワークの作成」に取り組んでみましたが、いずれも挫折したというか忙しくなって放置しているうちに飽きました。
そんな訳で、そろそろ気が向いてきたのでそのうち三度目にチャレンジします。
↑
ようするにこれが一番言いたかった
フレームワークとは何なのか
先日の日記にてそもそもの言葉の意味からアーキテクチャの定義を考えてみた訳ですが、知り合いから突っ込みを頂きました。
要約すると、
- フルスタックフレームワークを構築することは、アプリケーション・アーキテクチャを確定することの一部である。
- 何故ならば、フレームワークが担うのはあくまで汎用的な機能や処理パターンだからである。
- システム独自の機能というのは別途開発しなくてはならないはずであり、その独自の機能のところの構造まで確定させて初めて、アーキテクチャの確定と言えるのでは無いだろうか。
ということみたいです。
なるほど。
これは色々解釈が分かれるところかもしれませんが、私の解釈的には、
- フルスタックフレームワーク=(業務)アプリケーションとして基本的に必要な問題領域の全てをカバーしたフレームワーク
という認識で、問題領域には当然汎用的な問題領域もある要件に固有の問題領域も含んでおり、
- インターフェースで下位のクラスの挙動を束縛したり、UML等で概要だけ決めたり、代表的なところだけ作って後はそれを模倣させたり、と構造を確定させる手段は色々とあると思います。
こういうところもある程度含んでいるつもりでした*1。
しかし
「システムに固有の*2機能」というと、それは広義の意味においては「ビジネスロジック」であり、「システムに固有の機能の構造を決める」とは、「ビジネスロジックのモジュール設計」という解釈も成り立つと思うので、ビジネスロジックの設計は設計者がやるべきという解釈も成り立つような気もします。もちろん、
- インターフェースで下位のクラスの挙動を束縛したり、UML等で概要だけ決めたり、代表的なところだけ作って後はそれを模倣させたり、と構造を確定させる手段は色々とあると思います。
この辺の指針をアーキテクトが出した上でですけど。
では、フレームワークっていったいなんでしょう。IT用語辞典には、以下のように書かれています。
- 枠組み、下部構造、構造、組織という意味の英単語
・・・
アーキテクチャと非常に似ていますね。
しかし、私が日本人だからかIT業界の人間だからかJavaerだからかは分かりませんが、「アーキテクチャ」と言われるのと「フレームワーク」と言われるのとでは微妙にニュアンスが違います。
そこで
また、勝手に俺解釈をしてみます。
- アーキテクチャとは、基本構造の設計である。
- フレームワークとは、あるアーキテクチャを実装したものである。
- よって、アプリケーション・アーキテクチャの確定とは、アプリケーションの基本構造を設計し、それを実装することであり、即ちフルスタックフレームワークを作成することである。
うーむ・・・
やはり微妙に違和感は抜けませんが、後悔はしていません。異論は認めます。
そもそもの言葉の意味から考えるアーキテクチャの定義
昨日は「architecture」という元々の単語の意味を全く無視して勝手に俺定義をしてしまった訳ですが。
そもそも、「architecture」の元々の意味ってなんでしょう。
建築用語でもIT用語でも無い元々の意味的には、「構造」とか「構成」とかいう意味みたいですね。
構造と構成では微妙に意味合いも違うような気はしますが、ここでは
アーキテクチャとは、構造である
としてみましょう。即ち、システムアーキテクチャとはシステムの構造であり、ソフトウェアアーキテクチャとはソフトウェアの構造であり、アプリケーションアーキテクチャとはアプリケーションの構造であるわけです。
では、構造って何でしょう?
Yahooの国語辞書によると、構造とは、
- 一つのものを作り上げている部分部分の材料の組み合わせ方。また、そのようにして組み合わせてできたもの。仕組み。
- 物事を成り立たせている各要素の機能的な関連。また、そのようにして成り立っているものの全体。
だ、そうです*1。
ここで、「一つのもの」「物事」を「アプリケーション」、「材料」「各要素」を「コンポーネント」に置き換えてみると、
・・・
なんだかこれって各問題領域毎のフレームワークを組み合わせて作成したフルスタックフレームワークのことを言っているような・・・
やっぱり、そもそもの言葉の意味的に考えると、「アプリケーション・アーキテクチャの確定=各問題領域毎のフレームワーク・コンポーネントを組み合わせて要件に適したフルスタックフレームワークを構築すること」と考えても差し支え無いような気がしてきました。
まぁ、そもそもの言葉の意味的な定義なんで狭義な定義の域は出ないんでしょうけど。
*1: 三つめの意味は、ちょっと数学的なのでここでは割愛します。
アーキテクチャとは何であろう
最近仕事柄良く聞かれます。同じ部の人に。
- アーキテクトって何する人っすか
- アーキテクチャって何スカ。設計と何が違うんすか
- アーキテクト・グループ(私が所属している部署)って何する部署っすか
- アーキテクトがいないプロジェクトもありますけど、本当にアーキテクトって必要なんすか
とか。まぁ、4に関しては、
- 専任の高度なアーキテクトがいないだけで誰かが知らず知らずのうちにアーキテクトロールを押し付けられてる
- つまり、認識していないだけでアーキテクトロールを持つ人は存在する
- 他のシステムを構築するときに確定したアーキテクチャを流用していたりとか、パッケージなどの基本的にアーキテクチャが確定しているようなソリューションを利用していたりとか
のどっちかなだけで、専任かどうかとか技術的に高度な判断ができるかどうかとかはおいといて、アーキテクトロールを持ってる(持たされてる)人が全くいないプロジェクトって無いとは思いますけど。
そして、システムも複雑化してきた昨今、兼任アーキテクトでは中々技術的に高度な問題に対して(最適ではないにしても、少なくとも)間違いでは無い判断を下し続けつつ、本来のお仕事もこなすのはちょっと難しいんじゃなかろうかってところだと思うんですけどまぁそれはさておき。
じゃーアーキテクチャって何スカって言われると、万人に分かりやすく納得がいくように説明しようと思うとまだまだ未熟な私にはこれがなかなか難しい。
上記wikipediaのページにはそれぞれ「ソフトウェアアーキテクチャという用語に関して、万人が合意した厳密な定義は存在しない」「システムアーキテクチャの明確な定義はなく、各種団体が様々な定義を与えている」とか、まだまだみんな考え中みたいなことを言っているかと思えば、
上記@ITの記事には「アーキテクチャに関しては十分定義され尽くしている」とか書かれていますが、
とか正直私には意味がよく分かりません。『設計と進化の指針となる原理に体現された (略)構造』ってどんな構造っすか。
まぁそれはさておき。
人によっては、アプリケーションの各問題領域ごとのフレームワークやライブラリの選定・作成やそれらの使い方・組み合わせ方を規定し、全問題領域をカバーする、要件に適したフルスタックなフレームワークを作成することがアーキテクチャ確定であると思う人もいるのでは無いでしょうか。
というかまぁそれは私のことな訳ですが。
しかし、よくよく考えてみると、これは狭義の意味においてはアーキテクチャ確定のような気がしますが、だからといってフルスタックフレームワークが出来上がれば「後よろしく」と去って行くアーキテクトもいないと思います。
というか必ずしもフレームワークが必要な訳でも無いですし。
アーキテクチャを確定する人がアーキテクトだと考えれば、フルスタックフレームワークを作成し終わってもそれだけではアーキテクトの仕事は終わらない→フルスタックフレームワークを作成する以外にもアーキテクトには仕事がある→フルスタックフレームワークを作成するだけではアーキテクチャ確定は終わらない→語弊を恐れずに言い換えてみると、フルスタックフレームワークを作成するだけではアーキテクチャは確定しない→アーキテクチャ≠フルスタックフレームワーク(より正確に言うと、フルスタックフレームワーク ∈ アーキテクチャ)ということになるかと思います。
まぁようするに
何がいいたいのかというと、
実は決まった定義は無かったりするんじゃないか
ということです。そんな訳で、ちょっとお酒も入ったこの勢いで、広義の意味でのアーキテクチャを勝手に俺定義してみたいと思います。
アーキテクチャとは、要求をシステム化するための(主に技術的な)「手段」である
- システムの基本構造を決めることも
- Web化することも
- フレームワークを使用することも
- フレームワークを組み合わせることも
- その組み合わせ方を規定することも
- 開発ツールを選定することも
- 開発環境を整えることも
- 開発手法や開発プロセスを整備することも
- 全て、要求をシステム化するための手段である。
よって、
うむ。
ものすんごい俺定義
後悔はしていません。異論もお叱りも認めます。
google guiceのAOPで一括設定
google guiceのAOPというと、私の勝手なイメージですが、
上記のユーザーズガイドの「Intercepting Methods」の項にあるサンプルコードの、
binder.bindInterceptor( any(), annotatedWith(Transactional.class), new TransactionInterceptor() );
にあるようなイメージです。ようするに、
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD }) @BindingAnnotation public @interface CalledLogging { // NOB. }
こんなアノテーションがあったとして、
public interface Service { public void execute(); }
こんなサービスのインターフェースがあったとすると、
public class ServiceImpl implements Service { @CalledLogging public void execute() { System.out.println("サービスメソッド実行"); } }
こんな風に、AOP対象メソッドにアノテーションでマーキングするイメージです。これはこれでシンプルで分かりやすいのですが、上記のようにロギングをAOPで刷り込む場合、正直
アノテーションでマーキングするのも直接log4jでロギングするのも対して変わらん
と思うんですよね。トランザクション管理とかならともかく。業務Webアプリでは、とりあえず業務機能の実装を始めといて、要件が決まり次第後から監査ログ的なものを織り込むとかいうこともままあると思うのですが、そういう場合に後からロギング対象の全メソッドにいちいちアノテーションを入れて回るのも正直相当面倒くさいと思います。
面倒くさいだけならいいのですが、人のやることですからアノテーションを入れ忘れることもある訳ですし、ちょっと属人性にたより気味なところも個人的にはちょっぴり微妙です。
要するに
シンプルで分かりやすいアノテーションベースのAOPもいいですけど、設定ベースでがっつり一括で業務機能は修正すること無しにAOPってのもやっぱり必要だと思う今日この頃な訳です。
そんな訳で
ちょっぴり試してみました。要件的には、
- 「ServiceImpl」で終わるクラス名の、「Excecute」で終わるメソッドの実行前後のみにログ出力する
ということをやってみたいと思います。
まず、第一のサービスのインターフェースとして、
public interface FirstService { public String doExecute(); public String run(); }
こんなものを用意し、その実装として、
public class FirstServiceImpl implements FirstService { private SecondService secondService = null; @Inject public void setSecondService(SecondService secondService) { this.secondService = secondService; } public String doExecute() { return run(); } public String run() { return secondService.doExecute(); } }
こんなものを用意します。この「FirstServiceImpl」が利用している「SecondService」のインターフェースは、
public interface SecondService { public String doExecute(); public String run(); }
こんなものを用意し、実装は、
public class SecondServiceImpl implements SecondService { private ThirdDao thirdDao = null; @Inject public void setThirdDao(ThirdDao thirdDao) { this.thirdDao = thirdDao; } public String doExecute() { return run(); } public String run() { return thirdDao.doExecute(); } }
こんな感じにします。で、この「SecondServiceImpl」が利用している「ThirdDao」のインターフェースは
public interface ThirdDao { public String doExecute(); public String run(); }
こんな感じにし、その実装は
public class ThirdDaoImpl implements ThirdDao { public String doExecute() { return run(); } public String run() { return "Third dao executed."; } }
こんな感じであったとします。
で
ロギングを行うメソッドインターセプターは、AOPアライアンスが提供する「MethodInterceptor」をimplementsして、
public class AroundLoggingInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { String methodName = invocation.getMethod().getDeclaringClass().getName() + "#" + invocation.getMethod().getName(); // 開始ログ System.out.println("called : " + methodName); // メソッド実行 Object returnObj = invocation.proceed(); // 終了ログ System.out.println("end : " + methodName); return returnObj; } }
こんなのを作成したとします。
肝心なのは、これらの依存性を解決するためのModuleをどう作成するかな訳ですが。とりあえず、インターフェースと実装は、
public class SampleModule extends AbstractModule { @Override protected void configure() { bind(FirstService.class).to(FirstServiceImpl.class).in(Scopes.SINGLETON); bind(SecondService.class).to(SecondServiceImpl.class).in(Scopes.SINGLETON); bind(ThirdDao.class).to(ThirdDaoImpl.class).in(Scopes.SINGLETON); } }
こんな感じでバインドするとして、問題はAOPの方です。
google guiceにおいては一般的に、AOPはAbstractModuleが提供するbindInterceptor()メソッドを用いて設定するみたいですが、
上記のJavaDocによると、bindInterceptor()の引数は以下のようになっているようです。
bindInterceptor(Matcher<? super Class<?>> classMatcher, Matcher<? super Method> methodMatcher, MethodInterceptor... interceptors)
即ち、第一引数でAOP対象となるクラスの条件を指定し、第二引数でAOP対象となるメソッドを指定し、第三引数で実際にメソッドの実行前後に織り込むインターセプターを指定するようです。
そこで
classMatcherとして
public class ServiceClassMatcher extends AbstractMatcher<Class> { public boolean matches(Class clazz) { if (clazz.getName().endsWith("ServiceImpl")) { return true; } return false; } }
こんなクラスを作成し、methodMatcherとして
public class ServiceMethodMatcher extends AbstractMatcher<Method> { public boolean matches(Method t) { if (t.getName().endsWith("Execute")) { return true; } return false; } }
としてこんなクラスを作成し、AbstractModule拡張クラスを最終的に
public class SampleModule extends AbstractModule { @Override protected void configure() { bind(FirstService.class).to(FirstServiceImpl.class).in(Scopes.SINGLETON); bind(SecondService.class).to(SecondServiceImpl.class).in(Scopes.SINGLETON); bind(ThirdDao.class).to(ThirdDaoImpl.class).in(Scopes.SINGLETON); bindInterceptor(new ServiceClassMatcher(), new ServiceMethodMatcher(), new AroundLoggingInterceptor()); } }
こんな風にしてみたとします。そして、これらを利用するクライアントコードとして、
public class Client { public static void main(String[] args) { Injector injector = Guice.createInjector(new SampleModule()); FirstService service = injector.getInstance(FirstService.class); System.out.println(service.doExecute()); } }
こんなものを作って実行してみると・・・
called : org.tiba.impl.FirstServiceImpl#doExecute called : org.tiba.impl.SecondServiceImpl#doExecute end : org.tiba.impl.SecondServiceImpl#doExecute end : org.tiba.impl.FirstServiceImpl#doExecute
おぉぉぉぉぉぉぉぉぉつぉおぉつおぉぉぉぉ!!
見事に、「ServiceImpl」で終わるクラス名の、「Execute」で終わるメソッド名の実行前後にのみ、ロギングを織り込めました。
今回は、「AbstractMatcher」拡張クラスを静的に定義してみましたが、無名クラスを使ってAOPの条件を指定してみてもなかなか面白そうです。
Mavenはビルドに適したツールか?
今年の2月くらいの記事みたいなので、ひょっとしたらもう読んだ人も多いかもしれませんが、
こんな記事があったのですね。知りませんでした。要約すると、
- 大半の人は、Mavenというアイデア自体は素晴らしいと思っている。
- しかし、Mavenの実装に関しては、「素晴らしい」「既存のビルドツールよりはましだ」「複雑過ぎて使いこなせない」と議論が分かれている。
といったところのような気がします。「Mavenは言ってみればEJB2のように無駄に複雑過ぎるので覚える気にも使う気にもなれない」的な意見は、なかなか的を射ているような気がします。
また、IDEとの相性で言うと、
みたいで、私が「NetBeansのMavenプラグインはEclipseに比べてよくできてるかも」と思っていたのは、あながち間違いでは無いみたいです。
それはさておき。かくいう私はというと、
- プロジェクトのひな形の作成
- 依存性の推移的な解決
この辺りの機能は便利かなーと思うのですが、
- 痒いところに手が届かないというか、ビルドプロセスをカスタマイズしようと思うとちょっとめんどくさいというかシンプルじゃない
- Eclipseの特にWTPと相性が悪い
- mavenがクラスパスに依存ライブラリを追加しても認識しない(WEB-INF/libの下のjarしかデプロイしない)とか
- Eclipse3.2の頃の話なので、ひょっとしたら3.3とか3.4とかで修正されてるかもしれませんけど
- djUnitとかみたく、antタスクしか提供していなさそうなライブラリもときにはある
このあたりがめんどくさかったので、以前の日記に書いた通り、ベースはantで、部分的にmavenのコマンドをantタスクに組み込んで利用しているという形を取ることが多い訳なのですが。
早い話が、
- アイデア自体は素晴らしい
- でも、痒いところには手が届かない
- でも、便利な機能もたくさんある
- なので、便利なところはうまく取り入れて使いたい
なんていう、まぁありきたりの感想な訳なのですが。よぉーくくだんの記事を眺めてみると、
- 依存性管理システムの代用品としてIvyを推奨
って書いてあるじゃありませんか。
Ivy・・・?
なんでしょう。寡聞にして聞き覚えがありませんので、ぐぐってみました。
これのことでしょうか。
- Ivyは、柔軟性とシンプルさを重視した、ポピュラーな依存性管理システムです。
とかゆってますね。自ら「ポピュラー」とかいうのはどうだろうとも思う*1のですがまぁそれはおいといて。基本的には、こんな感じっぽいです。
- 柔軟性とシンプルさを重視している。
- スタンドアロンなツールとして利用することも可能だが、antと統合することにより、より良く動作するよう設計されている。
- 依存性を解決するためのリポジトリとして、以下の三つのリポジトリを想定している
- 使い方はこんな感じ。
- こんな感じのIvyの設定ファイル(ivy.xml)を書いて、
<ivy-module version="2.0"> <info organisation="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>
-
- こんな感じのantのbuild.xmlを書く。
<project xmlns:ivy="antlib:org.apache.ivy.ant" name="hello-ivy" default="run"> ... <!-- ================================= target: resolve ================================= --> <target name="resolve" description="--> retrieve dependencies with ivy"> <ivy:retrieve /> </target> </project>
ほっほー。
元々、mavenで一番便利だなーと思ってたのは依存性の解決だし、今現在私はそれをantタスクを通して実行しているので(Eclipseの場合ですが)、ひょっとしたらこれは結構いけるかも?この辺もうまくいけるようならmavenから乗り換えてみてもいいかも。
そんな訳でちょっと試してきます。
*1: 結構、あちらの方は自ら「ベリーセキュア」とか平気で名乗りますよね。
MacにNetBeansでMercurialその6 - ちょっぴりした前進と新たなる課題
先日、一念発起してLeopardにバージョンアップしたもののやっぱりNetBeansからコミットできなかったMercurialな訳ですが。
今日、NetBeansを起動してみると「19個程プラグインが新しくなったのでアップデートせいや」というお達しがポップアップされたので、プラグインを新しくしてみました。
ら
Mercurial用プラグインもバージョンアップしているじゃあーりませんか。まぁだからといってどうだということもないだろうとも思ったのですが、なんとなく念のためもう一度Mercurialを試してみました。
・・・
おぉぉぉぉっぉおぉぉぉっぉおぉぉっ!?
なんか、コミットできるようになってるじゃありませんか!
・・・
・・・
・・・って、ちょっと待て
や っ ぱ り バ グ だ っ た の か orz
なんだか軽い脱力感が。この件に関してはけっこー一所懸命がんばって調べたのに。まぁそれはさておき、この勢いに乗ってついでに以前に調べた方法で公開リポジトリを作成してプッシュも試してみました。
が
以前に会社で試したときのWindowsと同じように、プッシュはうんともすんとも言いません。世の中、そううまく行かないようです。
WindowsでもMacでもうまく行かないんだし、どーせこれもバグかなーなんてまたちょっとNetBeansを疑ってしまいつつ、とりあえずコマンドからもプッシュしてみることに。
$ hg push pushing to http://localhost:8000/ searching for changes ssl required
ssl required・・・?
どうやら、コマンドから実行しても失敗するようです。しかも、なんだかSSLが要求されているようです。加えて、
os.environ["HGENCODING"] = "UTF-8"
と設定しても、日本語でコミットできません。
うぅむ。
しかし、そもそもコミットできなかったときと比べるとなんとくなんとかなりそうな気配が。
ちょっぴりまたやる気になってきました。