BT

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

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル Guice(ジュース)を早飲みしすぎていませんか?

Guice(ジュース)を早飲みしすぎていませんか?

ブックマーク

あなたのチームが、既存アプリケーションを「シングルトンの入れ子」設計から依存性注入(DI)へ移行しようとしているなら、この論文に心引かれるでしょうが、DIへの移行は難しいことが分かっています。ソースベースの大部分を巻き込むことなくコミットさせるのが望ましいのですが、大型アプリケーションにDIをレトロフィットする場合、難しいことがあります。この論文はレガシーをDIに移行する一般的なケースにも当てはまり、また、論文にはGoogleのJava DIコンテナ(Guice)の名を入れていますが、Javaや.NET、Python、Rubyなどにも当てはまります。PicoContainerやSpringなどよりも、まさに働き者というわけです。

シングルトンは誕生してしまうものです

シングルトンと静的状態は長年にわたって、問題含みとして非難の的となってきました。

不規則に広がったシングルトンのヘアボールと比較すると、DIのアプリケーションの方がテストしやすく、よりクリーンなアーキテクチャであることに間違いありません。しかし、時間的なプレッシャーの下では、単純なシングルトンを使えば、コンポーネント間の大きなギャップを埋め易くなることも多々あります。1つや2つのシングルトンで始まったものが、最終的には何十、何百といったシングルトンになることさえあり、ついにはチーム全体が、コードは維持不可能、とお手上げ宣言することになります。チーム主任はリライトを命令する予定にしておらず、前途明るいDIが気に入っているため、DIに向けて現在のコードベースをリファクタリングするのが実践的と思われます。

DIパラダイスに向かって方向転換を決めたらすぐに、とるべき道が分かっていなければなりません。もちろん、機能開発とバグ修正をやめて、ただ目標に向かって進むこともできますが、正しい方法ではありません。その代わりに、新しい機能性の同時実現を望んでいるのですが、経験からすると、1?2週間でGuice(あるいはSpringなど)を入れるという約束が達成されるのは、結局その何ヵ月も先になってしまうのです。たとえDIの作業を担当する部署が、機能とバグ修正のコーディングを行っている部署と同一でないとしても、DIリファクタリング完成時にマージ地獄が待っている危険性があります。

どこから手をつけるべきかについても、定かではありません。登録済みのコンポーネントなしで、もうDIコンテナをmain()に入れてしまい、その変更をまずコミットするのでしょうか。それともWeb層から始めて、DIに向かって前進するのでしょうか。また、DIコンテナには当初シングルトンのルックアップの結果を投入し、後で立ち戻るようTODOコメントを入れておくのでしょうか。いずれも面倒で不愉快な事態に陥る可能性があります。

依存性注入手段としての「Service Locator」(サービスロケータ)

Martin Fowler氏は2003年に依存性注入に関する非常に信頼のおける論文(リンク)を書きました。当時、DIの正式分野は未成熟で、Fowler氏は価値ある代替物としてサービスロケータを論じたのです。もちろん、人によって異なる見方があるでしょうが、ここではメソッド付きのシングルクラス(シングルトンそのもの)を意味すると仮定しますので、つまりこのようなことです…。

public Object getService(String serviceName) {
// etc
}

or

public T getService(Class serviceType);
// etc
}

ブートのような場所(mainメソッド?)にはサービス名称の適切なインスタンスが投入され、本当のインスタンスとモックインスタンスの混合を使って単体テストがプログラムできるという考え方です。レガシーコードベース内にシングルトンのルックアップがある場合はいつでも、代わりにサービスロケータを用いて、同じコンポーネントをルックアップするように小さな変更を加えます。この方法がうまくいくのは「アプリケーションスコープ」のサービス/コンポーネントだけです。最新のWebフレームワークにはセッションと要求がスコープになっているコンポーネントがあり、そのスコープになると、フレームワークが当該コンポーネント向けに依存性注入を処理します。DIを目指しているなら、アプリケーションにはまだ最新のWebフレームワークがない公算が高いので、しばらくは現状に甘んじることにしましょう。サービスロケータを本源のブート場所に投入する必要があります…。

public void setService(String serviceName, Object implementation) {
// etc
}

安全を期するためには、サービスロケータをロックし、リードオンリーにするメカニズムが必要です。ライフサイクル開始を呼び出す前には必ず、mainメソッドの最後でlock()メソッドを呼び出せば、非常に効率的に誤用を防げるでしょう。

サービスロケータの配置はほんの手始めです。次に、シングルトンのgetInstance()メソッドをサービスロケータ上のgetService()に置き換えるという、一連の小さなコミットを行います。要するに、こうしたシングルトンは、「管理された単一インスタンス」になったのです。問題のコンポーネントのインターフェース/implを切り離す好機がやってきたと思われるかもしれません。その実行を決断する理由の一つにモッキング(googleのEasyMockやJMock、Mockito)の促進が挙げられます。なぜなら、当然のことながら、アプリケーションのテストカバレッジを同時に拡大したいという希望があるからです。

排除すべきシングルトンがなくなったら、サービスロケータを利用するコードフラグメントを再考できます。クラスでコンポーネント/サービスを必要とする場合はいつでも、getServiceが呼び出されるということかもしれません。同一クラスで複数回行われることさえあるかもしれません(スコープ外になるたびに、ゴミ集めが行われます)。その場合、コンストラクタで一度getServiceを呼び出し、メンバー変数として結果を保存しておけば気が利いているでしょう。

形式化したサービスロケータ・コンポーネント図(上)の場合、カリフォルニア(農業とハイテクを担当するコンポーネント)には、ゲーム機の機能を提供するネバダが必要です。オレゴン(ヘーゼルナッツ)とアリゾナ(さらにハイテク)。他については表示がありませんが、ご理解いただけたと思います。

前述したように、望ましい変更方法は小さなコミットを連続して行うことです。他がたやすくマージできるコミットです。改善済み機能性とバグ修正に関するチームのロールアウトへ、簡単に同時サブミットできます。ロールバックのリスクは低いはずです。この機に乗じて、新たに切り離したコンポーネントに小規模の単体テストも追加するのであれば、ロールバックのリスクはさらに低くなります。

まず、最も依存度が低く、最も依存されているコンポーネントから

シングルトン由来から切り離し、サービスロケータ設計に最初に移行するコンポーネントは、他のいかなるシングルトンにも依存してはいないけれども、依存されているかもしれないコンポーネントです。

つまり、コンポーネントを木の実にたとえるなら、一番下になっている実 -- 最も自らの依存度が低く、最も依存されているコンポーネントです。

また、小規模の単体テストを行った場合に、最も容易に高いカバレッジを達成できるコンポーネントでもあります。おそらく、お気に入りのモックライブラリの作業ということになるでしょう。1つ処理してコミットすれば、別のコンポーネントが「最も自らの依存度が低く、最も依存されている」という条件を満たすようになるでしょう。

Guiceの出番です

これで、単一のサービスロケータを介してアクセス可能な多数のコンポーネントで構成されたアプリケーションができあがったので、Guice(あるいは好みのDIコンテナ)を配置し、サービスロケータからGuiceモジュール(好みのDIコンテナの設定用語に置き換えてください)へコンポーネントを移動し始める時がやってきました。

最も系統的なやり方は、コンストラクタ内でサービスロケータのルックアップを1つ見つけ、そのルックアップをインスタンス化しているクラスへプッシュすることです。同時にコンストラクタ引数に変更し、呼び出し側が同一の依存性に対してメンバー変数を持つようにしてください。プッシュを続けていると、遅かれ早かれ、mainメソッドにたどり着きます。その時点になると、Guiceに安全に管理されていることになります。

DIコンテナがすべてを管理するようになったら、これまでよく働いてくれたサービスロケータに感謝しつつ、削除してかまいません。

クラス/コンポーネントに引数の長いリストができる場合がある、という若干の副作用があります。アプリケーション設計には若干の作業が必要というヒントでしょう。その時点でファサードを作成すれば理想的であることが多々あります。

多数の企業がこの系統的なアプローチを使って、ある程度の成功を収めてきました。このアプローチがうまくいくのは、何百ものコンポーネントが存在し、そのコンポーネントがシングルトンの入れ子として始まり、現在では軽量の依存性注入になっている場合です。また、EJB 3.0を目的としている場合もうまくいきます。マージを促進するコードフリーズが皆無で、標準のコーディングに伴って同時に発生可能なロールアウト、というエクスペリエンスでもあるのです。

補足説明

依存性注入は制御の反転(Inversion of Control:パターンとなってから10年が経過)の一部にすぎません。依存性注入以外に、コンフィギュレーションとライフサイクルという2つの側面が存在します。どうやら、クラスのコンフィギュレーション(よりいっそうの注入)ならびにライフサイクルの状態変更は、同様に外側から制御すべきということのようです。独自のconfigスレッドやspawnスレッドを調達したり、コンストラクタでソケットや、なお悪い静的初期化子(static initializer)をリスンしたりすべきではないのです。

著者について

Paul Hammant氏は2000年以来、制御の反転(Inversion of Control)を奨励しており、元々はApacheのAvalonフレームワーク上で奨励していましたが、その後2003年にはPicoContainerを使ったConstructor Injectionを開発しました。サンフランシスコのThoughtWorksに勤務しています。

 

原文はこちらです:http://www.infoq.com/articles/drinking-your-guice-too-quickly
(このArticleは2008年4月1日に原文が掲載されました)

この記事に星をつける

おすすめ度
スタイル

BT