Akka Play guice bindings tagless final (TF) support

105 views Asked by At

I have this binding to configure the Logger[IO] in my app (module with this line is in guice.conf file):


class CatsEffectModule extends AbstractModule with ScalaModule {

  override def configure(): Unit = {
    bind[Logger[IO]].toInstance(Slf4jLogger.getLogger[IO])
  }

}

Then in the app i can do this:

@Singleton
class MyClass @Inject()(implicit logger: Logger[IO]) { ... }

And this works fine in the application.

But it won't work when used in GuiceInjectorBuilder (for tests):

import play.api.inject.guice.GuiceInjectorBuilder


private val application: Injector = new GuiceInjectorBuilder()
    .bindings(bind[ExecutionContext].to(ExecutionContext.global))
    .bindings(bind[ApplicationLifecycle].to[DefaultApplicationLifecycle])
    .bindings(new CatsEffectModule())
    .build()

application.instanceOf[MyClass]

It gives me an error:

No implementation for io.chrisdavenport.log4cats.Logger was bound.
[info]   Did you mean?
[info]     io.chrisdavenport.log4cats.Logger<cats.effect.IO> bound  at guice.CatsEffectModule.configure(CatsEffectModule.scala:21) (via modules: com.google.inject.util.Modules$OverrideModule -> guice.CatsEffectModule)

And every TF-entity injection in tests fails like this. Is there some difference between how Akka Play runs Guice and how GuiceInjectorBuilder works?

Code example: https://github.com/DenisNovac/play-tf-test

1

There are 1 answers

0
DenisNovac On BEST ANSWER

The Scala-guice by codingwell (https://github.com/codingwell/scala-guice) allows to bind TF-classes (it is made for vanilla Guice, not the Play Guice). They will be binded to dependent classes correctly, but the GuiceInjectorBuilder won't let you get it by instanceOf method.

But it seems that it works in both directions if you use both binds:

import cats.effect.IO
import com.google.inject.AbstractModule
import net.codingwell.scalaguice.ScalaModule
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import play.api.inject.guice.GuiceInjectorBuilder
import play.api.inject.{ApplicationLifecycle, DefaultApplicationLifecycle, Injector, bind}

import scala.concurrent.ExecutionContext

class WorkingModule extends AbstractModule with ScalaModule {

  override def configure(): Unit =
    bind[CustomTFInterface[IO]].toInstance(new CustomTFInterfaceImpl)
}

class HomeControllerSpec extends AnyFlatSpec with Matchers {

  it should "test1" in {
    val application: Injector = new GuiceInjectorBuilder()
      .bindings(bind[ExecutionContext].to(ExecutionContext.global))
      .bindings(bind[ApplicationLifecycle].to[DefaultApplicationLifecycle])
      .bindings(bind[CustomTFInterface[IO]].toInstance(new CustomTFInterfaceImpl))
      .bindings(new CatsEffectModule())
      .injector()

    application.instanceOf[CustomTFInterface[IO]] // works
    //application.instanceOf[InjecableWithTfDependencies] // fails

  }

  it should "test2" in {
    val application: Injector = new GuiceInjectorBuilder()
      .bindings(bind[ExecutionContext].to(ExecutionContext.global))
      .bindings(bind[ApplicationLifecycle].to[DefaultApplicationLifecycle])
      .bindings(new WorkingModule())
      .bindings(new CatsEffectModule())
      .injector()

    //application.instanceOf[CustomTFInterface[IO]] // fails
    application.instanceOf[InjecableWithTfDependencies] // works
  }

  it should "test3" in {
    val application: Injector = new GuiceInjectorBuilder()
      .bindings(bind[ExecutionContext].to(ExecutionContext.global))
      .bindings(bind[ApplicationLifecycle].to[DefaultApplicationLifecycle])
      // both binds together also works
      .bindings(new WorkingModule())
      .bindings(bind[CustomTFInterface[IO]].toInstance(new CustomTFInterfaceImpl))
      .bindings(new CatsEffectModule())
      .injector()

    application.instanceOf[CustomTFInterface[IO]]       // works
    application.instanceOf[InjecableWithTfDependencies] // works
  }

}

Perhaps there is a way to make scala-guice and play work together but i got no luck with it.

Full example: https://github.com/DenisNovac/play-tf-test