Java
Scala
DependencyInjection
GoogleGuice

ScalaとGoogle Guiceの組み合わせについて軽く調べた

More than 3 years have passed since last update.

あまり目新しい内容ではないですが、Google Guice理解のために、改めてまとめました。


ScalaにおけるDI

Javaなどの言語と同様に、Scalaにおいても依存性注入(DI)を実現する方法はいくつかあるようです。


  1. Constructor InjectionやSetter Injectionを使う

  2. Cake PatternなどのScala特有の言語機能を使った方法

  3. (Google Guiceなどの)DIフレームワークを使う

  4. 構造的部分型を使うetc....

この記事では3.のGoogle Guiceを使う方法について調べました。そういえばPlay Frameworkの2.4でも導入されるとか聞いたような……?


Google Guiceの基本的な使い方(Java)

いくつかの典型的なパターンと比較しているという点でも、公式のチュートリアルがとても分かりやすいかなと思います。GuiceはもともとJava用なので、Javaで書いてある……。

大事な部分だけ抜き出すとこんな感じ。

interface Service {

void doSomething();
}

class RealService implements Service {
@Override
public void doSomething() {
// .....
}
}

class FakeService implements Service {
@Override
public void doSomething() {
// .....
}
}

やりたいことは、RealServiceFakeServiceどちらを使うかを状況によって切り替えることです。例えばテスト時にだけFakeServiceを使うようにしたい。そして、どの具体的な実装を使うかという知識は、Serviceとは離れた場所に持たせたい。Guiceでは次のようなお作法に従ってDIを実現するようです。


  • 依存性を注入されるクラスでは、依存関係にあるクラスをコンストラクタで受け取る


    • コンストラクタには@Injectアノテーションを付けておく



  • どう配線するか(どのインタフェースにどの具体的な実装を入れるか)はModuleにまとめる


  • Guice.createInjectorを用いることで、ModuleからInjectorを生成する


  • InjectorgetInstanceメソッドを呼ぶことで、Moduleで記述された参照関係に基づいて実際のインスタンスが得られる

このルールに基づいて、Serviceを利用するクラスであるServiceUserを記述してみます。

class ServiceUser {

private final Service service;

@Inject
public ServiceUser(Service service) {
this.service = service;
}
// serviceを利用したコードは省略...
}

具体的にどのServiceを利用するかは、ServiceUserに現れていません。次はようやく、ServiceUserを使う例です。

public class ServiceModule extends AbstractModule {

@Override
protected void configure() {
bind(Service.class).to(RealService.class);
}
}

public static void main(String[] args) {

Injector injector = Guice.createInjector(new ServiceModule());
ServiceUser serviceUser = injector.getInstance(ServiceUser.class);
// serviceUserを使ったコードは省略.....
}

Moduleを切り替えることで、依存する実装も切り替えることができるようになりました。


ScalaでGuiceはどうなるのか

依存関係にGoogle Guice 4.0を追加します。

libraryDependencies += "com.google.inject" % "guice" % "4.0"

Scalaでの使い方としては、Javaとほとんど同じように書けます。@Injectの位置が、クラス名の後ろ、コンストラクタ引数の前になるという点に注意。

object TestDI {

import javax.inject._
import com.google.inject.AbstractModule

trait Service {
def doSomething(): Unit
}

class RealService extends Service {
def doSomething() { println("This is RealService") }
}

class FakeService extends Service {
def doSomething() { println("This is FakeService") }
}

class ServiceUser @Inject() (service: Service) {
def use() { service.doSomething() }
}

class ServiceModule extends AbstractModule {
override protected def configure() {
bind(classOf[Service]).to(classOf[RealService])
}
}
}

使う時もJavaとほぼ同様です。

import com.google.inject._

val injector: Injector = Guice.createInjector(new ServiceModule)
val user: ServiceUser = injector.getInstance(classOf[ServiceUser])
user.use //=> "This is RealService"


まとめ

Scalaでも、おおむねJavaと同じようにGuiceは使えることが分かりました……が、JavaのものをそのままScalaに移しても、あんまりScalaっぽくならないので微妙感が漂う気がします。なんかもうちょっとラップしたライブラリが欲しくなるなる……。

Playなどのフレームワークと統合した場合にGuiceはどういった使い勝手になるのかについては、今後調査したいですね……。


参考にしたもの