PlayFramework+GuiceでDIしているときに型クラスインスタンスでインジェクションされたクラスを使用する

PlayFrameworkでGuiceを使ってDIしているときに、 型クラスインスタンスでインジェクションされたクラスを使う方法を調べました。

以下のようなコードがあるとします。

// Factory.scala

trait Factory[T] {
  def create: T
}

case class Type1()

object Type1Factory {
  implicit object Type1Factory extend Factory[Type1] {
     override def create: Type1  = Type1()
  }
}

case class Type2(value: String)

object Type2Factory {
  implicit object Type2Factory extend Factory[Type2] {
     //  どうやってここに、type2Repositoryを渡せば良い?
     override def create: Type2 = type2Repository.load()  
  }
}
// UseCase.scala

class UseCase @Inject()(type2Repository: Type2Repository) {
  def create[T: Factory]: T = {
    // 共通処理
    ... 

    implicitly[Factory[T]].create
  }
}
// Controller.scala

class Controller @Inject()(usecase: UseCase) {
  import Factory._
  def create1: Type1  = usecase.create()
  def create2: Type2 = usecase.create()
}

Factoryというcreateというメソッドをもつ型クラスがあって、 その型クラスインスタンスとしてType1FactoryType2Factoryが定義されています。 Type1Factoryはとくに他に依存しないcreateメソッドを持ちますが、 Type2Factorycreateメソッドでは、Type2Repositoryを使う必要があります。 Type2Repositoryはインタフェースで、他のところにDBからデータを取得する処理を行うType2RepositoryImplクラスが定義されています。 Type2RepositoryGuiceUseCaseクラスにInjectされます。

このとき、DI管理下のType2RepositoryインスタンスType2Factoryに渡す方法がわからず、困っていました。 Type2Repositoryが必要なのはType2Factoryだけなので、Factoryのメソッドの引数にType2Repositoryを渡したくありません。 型クラスインスタンスが増え、そのインスタンスが別のRepositoryを要求する場合、引数を追加していかなければならないためです。

良い方法が思いつかなくて悩んでいたところ、Twitterで教えていただきました。

値に対するimportでうまくいきそうです。以下が、改良版のコードになります。

// UseCase.scala

class UseCase @Inject()(type2Repository: Type2Repository) {

  def create[T: Factory]: T = {
    // 共通処理
    ... 

    implicitly[Factory[T]].create
  }

  implicit val type1Factory: Factory[Type1] = new Factory[Type1] {
    override def create: Type1  = Type1()
  }
  
  implicit val Type2Factory: Factory[Type2] = new Factory[Type2] {
     override def create: Type2 = type2Repository.load()  
  }
}
// Controller.scala

class Controller @Inject()(usecase: UseCase) {
  import usecase._
  def create1: Type1  = usecase.create()
  def create2: Type2 = usecase.create()
}

型クラスインスタンスをDI管理下のUseCaseクラスで定義することで、Type2Repositoryを呼び出すことができました。 また、先程はControllerクラスでimport Factory._で型クラスインスタンスをimportしていましたが、 scalaでは値に対してimportできるようなので、インジェクトされたUseCaseインスタンスに対してimportすることでimplicitが解決できるようになります。