Guice 1.0 User's Guide和訳 その5

はじめに


前へ | 目次 | 次へ

Guice vs. Dependency Injection By Hand - Guiceと手動DIの比較

お分かりの通り、Guiceを用いればファクトリークラスをいくつもいくつも作る必要はありません。クライアントの(サービスへの)依存性を解決するための明示的なコードを記述する必要は無いのです。もし依存性の解決を忘れてしまった場合は、Guiceは起動に失敗します。Guiceは、循環する依存性も上手く解決してくれます。

Guiceでは、スコープを明示的に、宣言的に指定できます。例えば、Guiceではセッションにオブジェクトを格納するための似たようなコードを何度も何度も記述する必要はありません。

現実においては、実行時に動的に実装クラスを決定するようなケースがしばしばあるかと思います。そういった場合はメタファクトリーやサービスロケータを作成することが一般的ですが、Guiceではそれらの労力を最小限にする「工夫」がなされています。

依存性の解決を手動で行う場合、DIというコンセプトに慣れていない場合は特にですが、古の習慣に捕われたり、結局直接オブジェクトに依存してしまったりしがちです。Guiceを用いれば、そのような現状を容易に一変させることができます。Guiceを使うことにより、全てがうまくいきます。

Guice 1.0 User's Guide和訳 その4

はじめに


前へ | 目次 | 次へ

Dependency Injection with Guice - Guiceを用いたDI

全てのサービスとそのクライアントに対してファクトリーを作り、その依存性を手動で解決するためのロジックを作成するのは「退屈」です。他のDIフレームワークのうちのいくつかは、サービスをインジェクションする個所を明示的に設定する必要さえあります。

Guiceは、これらの「お決まりの設定」を、メンテナンス性を犠牲にすること無く排除することを目標にしています。

Guiceではまず、「モジュール」を作成します。Guiceは、モジュールに対してバインダーを提供します。モジュールでは、このバインダーを利用してインターフェースとその実装をマップします。次のモジュールでは、Serviceに対してServiceImplがシングルトンスコープでマップされます。

public class MyModule implements Module {
  public void configure(Binder binder) {
    binder.bind(Service.class)
      .to(ServiceImpl.class)
      .in(Scopes.SINGLETON);
  }
}

モジュールを使って、「何をインジェクションしたいか」が設定できます。では、「何処にインジェクションしたいか」はどうやって設定すればよいのでしょう?Guiceでは、コンストラクタやメソッド、フィールドに対して「@Inject」アノテーションを利用します。

public class Client {

  private final Service service;

  @Inject
  public Client(Service service) {
    this.service = service;
  }

  public void go() {
    service.go();
  }
}

@Injectアノテーションを用いることにより、クラスのどのメンバーがインジェクションされるのかが明確に分かるようになります。

クライアントをインジェクションする場合は、直接クライアントのインスタンスを取得するか、クライアントをインジェクションされた他のクラスを使用するかのどちらかの方法を用います。

google guiceでインジェクションするクラスを実行時に指定する方法

はじめに


前へ | 目次 | 次へ

インジェクションするクラスを実行時に指定する方法

基本的なインジェクションの回でも述べましたが、google guiceでのDIは以下のように、

public class TestModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(Service.class).to(ServiceImpl.class).in(Scopes.SINGLETON);
        bind(Client.class).to(Client.class).in(Scopes.SINGLETON);
    }
}

基本的にインジェクションする実装はモジュール内にハードコーディングされています(上記の例でいうところの「ServiceImpl」)。これはこれで、実装クラス名や実装クラスそのものが変更になった場合とかもリファクタリングとか補完が効いたりとかIDEの恩恵が受けれて便利なのですが*1、次のようなサービスの場合はどうでしょうか。

  • 他システム接続モジュール*2を内部的に呼び出すサービス
  • 単体テスト環境・結合テスト環境では他システムには接続できないので、何もしないダミーの実装をインジェクションする
  • 総合テスト環境および本番では、実際に他システムに接続する本物の実装をインジェクションする

デプロイ環境毎にコードを修正するのはあまりにも微妙です。「必ず万人がそうする」とまではいいませんが、「設定ファイルベースでインジェクションする実装を動的に切り替える」というニーズは、やはりありそうな気がします。

  • 仕変以外は実装が変更することが無いようなインジェクションはモジュール内で静的に
  • 環境によってしょっちゅう実装が変更するようなインジェクションは設定ファイルベースで動的に

という、ハイブリッドなアプローチが個人的には良いような気がします。google guiceの場合、動的に実行時にインジェクションするクラスを決定するにはどうすればいいのでしょう。とりあえず、

public class DynamicInjectorModule extends AbstractModule {
    @Override
    protected void configure() {
        Class<?> clazz = null;
        Class<? extends Service> sClass = null;
        String className = "org.chiba.impl.ServiceImpl";
        try {
            clazz = Class.forName(className);
            sClass = clazz.asSubclass(Service.class);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        
        bind(Service.class).to(sClass).in(Scopes.SINGLETON);
    }
}

と書けばモジュールクラスはサービスの実装(ServiceImpl)に直接依存しなくなりますが、問題は

        String className = "org.chiba.impl.ServiceImpl";

ここです。例えばここを、

        String className = PropertyUtil.getProperty(Service.class.getName());

とかみたいに書ければ、実装クラスに関する情報をプロパティファイルに外出しして実行時に動的に決定できそうです。そんな訳で、続きは「設定ファイルとの連携」で検討することにします。

*1: SpringやStrutsにありがちな、設計ファイルのミスが実行時にならないと分からないということが無い(コンパイルエラーとして静的に検出できる)。

*2: WebサービスでもMQでもなんでもいいですけど

Guice 1.0 User's Guide和訳 その3

はじめに


前へ | 目次 | 次へ

Dependency Injection By Hand - 手動DI

DIパターンはユニットテストをより簡単にすることを目的としたデザインパターンです。DIパターンを用いるにあたっては、必ずしもフレームワークが必要であるという訳ではありません。DIパターンにおけるおよそ8割のコードは十分自作するこも可能です。

先ほどの例ではクライアントがファクトリーを呼び出してサービスを取得していましたが、DIパターンでは、クライアントはサービスを実行する際には既に依存性を解決されています。いわゆる、「私に電話しないで下さい。用があればこちらから電話します。」(ハリウッドの原則)と呼ばれるパターンです。

public class Client {
    
  private final Service service;

  public Client(Service service) {
    this.service = service;
  }

  public void go() {
    service.go();
  }
}

これにより、ユニットテストはかなり簡単になります。モックサービスを用いてテストを実行すれば、その後は特に「後片付け」などを行う必要はありません。

public void testClient() {
  MockService mock = new MockService();
  Client client = new Client(mock);
  client.go();
  assertTrue(mock.isGone());
}

クライアントが何に依存しているかも、APIから正確に判断することができます。

では、どのようにすれば(クライアントをサービス実装に依存させること無しに)サービス実装を既に取得したクライアントを取得することができるのでしょう?手動でDIを実行する場合は、全ての依存性に関するロジックはファクトリークラスに隠蔽する必要があります。つまり、以下のようなクライアント用のファクトリークラスを作成しなければならないということになります。

public static class ClientFactory {

  private ClientFactory() {}

  public static Client getInstance() {
    Service service = ServiceFactory.getInstance();
    return new Client(service);
  }
}

結局のところ、手動でDIを実行する場合も、昔ながらのファクトリーパターンを用いる場合と同等量のコードを記述する必要があることになります。

人は何故PKに人工キーを用いるのか

残念ながら私はそんなにモデリングに関する知見は無いので、「自然キー分かりやすくて最高www人工キーなんて分かりにくいだけw」なんていう人を納得させるだけの明快なメリットを示せるような見解は持ちあわせておりません。漠然と、

  • メリット
    • 変更に強い
    • 自然キーの場合、複合キーがめんどくさい
    • 最近のデータベースアクセスソリューションもの(平たく言うと、広義の意味でのORM)と相性が良い
  • デメリット
    • 関連が分かりづらい
    • ベテランの技術者は自然キーに慣れている人が多く、なかなかコミットしてくれない
    • 既存システムはまだまだ自然キーのものが多いので、他システム連携的なところとは相性が悪いことが多い

くらいにしか思っておらず、ようするにビジネス的な変更がシステムに与える影響を小さくする、一種の粗結合的なものだと思うのですが、

  • 変更に強い
  • 最近のデータベースアクセスソリューションもの(平たく言うと、広義の意味でORM)と相性が良い

個人的にはこれだけでも十分メリットのような気がするので、

  • 理想的にはPKは人工キー
  • でも、現実的には自然キーをPKにせざるを得ない局面も多いかも(エンタープライズだと特に)

と、いうのが流行りだと勝手に思っていた訳ですが。

自然キーの「分かりやすさ」「理解しやすさ」「作りやすさ(実装しやすさ)」は密結合的なそれであり、最初に作るときには分かりやすくて作りやすいが、保守や仕変対応的なものも含めたTCO的な意味での「作りやすさ」とはまたちょっと違うと思っていた訳なのですが。

一方、私の上司は、私よりも上の世代の人だからかドットネッターだからか単なる個人の好みかそれともこれまでの現場がそうだったからなのかは分かりませんが、バリバリの自然キー派のようです。世間一般的なパターンは自然キーであり、人工キーを使うパターンは特殊なパターンだそうです。ケースによっては人工キーを使う局面もあるかもしれないが、過去も現在もこれからも、人工キーが主流になることなど有り得ないそうです。「人工キーがお勧めだなんて書いてあるモデリングの本なんて見たことが無い。あったら持ってこい*1」とまでおっしゃっています。

「変更に強い」なんて主張しても、「例えば受注番号の場合だとあれをこうしてそれをそうして・・・すれば人工キーなど別に要らん。明確なメリットがないのに自然キーの分かりやすさを捨てる意味が分からん」と、「自然キーは分かりやすい」の一点張りです。「変更に強い」とかそういう粗結合的なものは彼にとっては明確なメリットでは無いようです*2 *3

ここまで書いときながらいうのもなんなのですが、正直なところ私は、「別に自然キーでも人工キーでもどっちでもやってやれんことは無いと思うのでどうしても「俺は○○キー派だ!」と譲らない人がいれば実のところどっちでも良かったりするけど、「好きにしろ」と言われれば既存システムの足枷とかが無い限りは人工キー派かな」なんてユルい気持ちだったりするのですが、実際のところどうなんでしょうね。

実は、2:8理論でいうところの2が自然キー派で、8が人工キー派だなんていうオチもあるのかもしれませんけど。

*1: 「楽々ERDレッスン」とかはそんなことが書いてあるような気がするのですが・・・

*2: むしろ、「分かりにくくなる」といったデメリット的な側面が強いようです。

*3: 私はJaverなのでよくわからないのですが、.Netではあまりインターフェースをきったりとかはしないのでしょうか。

google guiceで定数をインジェクション

はじめに


前へ | 目次 | 次へ

文字列のインジェクション

例えば、Springの場合、以下のデータソースの定義のように

<bean id="dataSource" 
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
    <property name="url" value="jdbc:hsqldb:hsql://localhost:9001" />
    <property name="username" value="sa" />
    <property name="password" value="" />
</bean>

依存するクラスのインスタンスでは無く何かしらの定数をインジェクションしたくなることはよくあると思います。上記の例のような環境依存的なものは、プログラム中にハードコーディングするのでは無くBean定義ファイルやプロパティファイル等に外出しするのが定石では無いでしょうか。

google guiceではそのような場合、以下のようにして定数をインジェクションすることが可能なようです。

  • 定数をインジェクションされるクラスのインターフェース
public interface ConstantInjectedService {
    public void execute();
}
  • 定数をインジェクションされるクラスの実装
public class ConstantInjectedServiceImpl implements ConstantInjectedService {
    @Inject
    @Named("name")
    private String name_;
    
    public void execute() {
        System.out.println("Injected name : " + name_);
    }
}
  • モジュールクラス
public class ConstantInjectorMocule extends AbstractModule {
    @Override
    protected void configure() {
        bindConstant().annotatedWith(Names.named("name")).to("Hoge");
        bind(ConstantInjectedService.class).to(ConstantInjectedServiceImpl.class).in(Scopes.SINGLETON);
    }
}
  • テスト用クラス
public class ConstantInjectTest {
    public static void main(String[] args) {
        Module module = new ConstantInjectorMocule();
        Injector injector = Guice.createInjector(module);
        ConstantInjectedService service = injector.getInstance(ConstantInjectedService.class);
        service.execute();
    }
}
  • 実行結果
Injected name : Hoge

上記は、例としてguiceが提供する「@Named」アノテーションを利用していますが、別にアノテーション自体はなんでもよくて、bindConstant().annotatedWith()メソッドとアノテーションを組み合わせてインジェクションするところがミソです。

一応、上記のようにアノテーションとbindConstant().annotatedWith()メソッドを利用して定数を(文字列じゃなくても)インジェクションすることは可能のようですが、正直に言うと個人的にはちょっと微妙です。

と、いうのも、結局のところモジュールクラスに定数をハードコーディングするのならばいわゆるコンスタントクラス的なものと大差無いような気がするし、上記の手法では「環境依存的な設定を外部ファイルに外出しする」といったことを仮にやりたくなったとしてもできません。やりたくならなけれべ別に構いませんけど、全ての人が絶対にやりたくならないとも言い切れませんので、この辺に関しては別途「設定ファイルとの連携」にて検討したいと思います。

コレクションのインジェクション

例えば、MapやListのような、総称型を含む型をインジェクションしようと思った場合、基本的には文字列のような単純な型と同じなのですが、ちょっぴりだけ難しくなります。例として、Mapをインジェクションするためのサンプルコードを以下に示します。

  • Mapをインジェクションされるクラスのインターフェース
public interface MapInjectedService {
    public void execute();
}
  • Mapをインジェクションされるクラスの実装
public class MapInjectedServiceImpl implements MapInjectedService {
    @Inject
    @Named("map")
    private Map<String, String> map_;

    public void execute() {
        for (String key : map_.keySet()) {
            String value = map_.get(key);
            System.out.println("key : " + key + ", value : " + value);
        }
    }
}
  • モジュールクラス
public class MapInjectorModule extends AbstractModule {
    @Override
    protected void configure() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
        map.put("key4", "value4");
        
        bind(new TypeLiteral<Map<String, String>>(){}).annotatedWith(Names.named("map")).toInstance(map);
        bind(MapInjectedService.class).to(MapInjectedServiceImpl.class).in(Scopes.SINGLETON);
    }
}
  • テスト用クラス
public class MapInjectTest {
    public static void main(String[] args) {
        Module module = new MapInjectorModule();
        Injector injector = Guice.createInjector(module);
        MapInjectedService service = injector.getInstance(MapInjectedService.class);
        service.execute();
    }
}
  • 実行結果
key : key1, value : value1
key : key3, value : value3
key : key2, value : value2
key : key4, value : value4


「TypeLiteral」を使っているところがミソですね。

Guice 1.0 User's Guide和訳 その2

はじめに


前へ | 目次 | 次へ

Plain Old Factories - 昔ながらのファクトリーパターン

DI(Dependency Injection)が用いられるようになる前は、ファクトリーパターンが最も一般的に用いられていました。サービスのインターフェースに加えて、クライアントに提供するサービスの実装クラスと、テスト用のモック実装の両方を提供するようなファクトリーを一緒に作成していたと思います。シンプルにするために、ここではシングルトンなサービスを例として扱うことにします。

public class ServiceFactory {

  private ServiceFactory() {}
    
  private static Service instance = new ServiceImpl();

  public static Service getInstance() {
    return instance;
  }
  
  public static void setInstance(Service service) {
    instance = service;
  }
}

クライアントは通常、サービスが必要になる度に直接ファクトリーを使用します。

public class Client {

  public void go() {
    Service service = ServiceFactory.getInstance();
    service.go();
  }
}

このクライアントはとてもシンプルです。しかし、クライアントに対するユニットテストはモックサービスを用いて行う必要があり、テスト後はモックを使用するための設定の「後片付け」を行う必要があります。このサンプルではそれらは大した手間ではありませんが、クライアントやサービスが増える毎にユニットテストは大変になるはずです。もしユニットテスト後に「後片付け」を忘れてしまったならば、他のユニットテストは本来失敗するべきなのに成功してしまったり、成功するべきなのに失敗してしまったりするかもしれません。場合によっては、実行順序を変えるだけでテストが成功してしまったり失敗してしまったりする可能性もあります。

public void testClient() {
  Service previous = ServiceFactory.getInstance();
  try {
    final MockService mock = new MockService();
    ServiceFactory.setInstance(mock);
    Client client = new Client();
    client.go();
    assertTrue(mock.isGone());
  } finally {
    ServiceFactory.setInstance(previous);
  }
}

最後にもう一つ、ファクトリーパターンはシングルトン的なアプローチになりがちだということを留意点としてあげておく必要があります。もしgetInstance()メソッドがシングルトンでは無い複数のインスタンスを返すならば、setInstance()メソッドの実装はより複雑なものになるからです。