I use Google Guice for dependency injection in most of my projects. I also use Akka Actors a lot for solving concurrency related tasks in my projects. I often write code like
val a = injector.getInstance(classOf[A])
val b = injector.getInstance(classOf[B])
val c = injector.getInstance(classOf[C])
val actorSystem = injector.getInstance(classOf[ActorSystem])
val myActorRef : ActorRef = actorSystem.actorOf(Props(new MyActor(a, b, c)))
And while this works. I still resent that now I have two ways of creating objects in my project. One via Guice approach of injector.getInstance
and then a second approach which is specific for actors actorOf(Props(...))
mechanism listed above. I always wished that I could use Guice consistently to get instances of actors and classes alike.
In this blog, we’ll try to do just that. Lets write up a minimal actor which will be used in this example.
package com.abhi.logic
class Logic {
def add(i: Int, j: Int) : Int = i + j
}
package com.abhi.actor
class MyActor(logic: Logic) extends Actor {
def receive = {
case msg: Msg => sender() ! logic.add(msg.i, msg.j)
}
}
case class Msg(i: Int, j: Int)
So I have written all my business logic in a simple class (which I can test easily) and now I am using my logic class as a dependency inside of the actor.
BTW, I am using the codingwell/scala-guice library to integrate Guice with my Scala project. Let us code up the Module required to perform all dependency injection.
class MyModule extends AbstractModule with ScalaModule{
def configure() : Unit = {
bind[ActorSystem].toInstance(ActorSystem())
bind[Logic]
}
@Provides
@Singleton
@Named("MyActor")
def getMyActor(actorSystem: ActorSystem, logic: Logic) = {
actorSystem.actorOf(Props(new MyActor(logic)))
}
}
You can see that I am using the @Named
approach to get the right instance. This is required because all actors in the end are created as ActorRef
so we can’t use the getInstance[T]
approach because the T is same for all actors. So we use names.
If our project had 100 actors. We will have one @provides
method for each actor with a unique name. We can easily specify all our dependencies as parameters of this method. Since they have been bound earlier in the configure method, we get them transparently in the @provides
method.
This also means that if tomorrow my actor starts depending on Logic1, Logic2, and Logic3 classes, I have just one place to make the change. the users of the actor don’t see the dependencies.
Now we need a small utility class which will make it easy for the clients to looking the named instances.
object ActorUtil {
implicit class ActorInjector(injector: Injector) = {
getActor(name: String) : ActorRef = {
injector.getInstance(Key.get(classOf[ActorRef], Names.named(name)))
}
}
}
We are creating this utility class so that our client code can easily lookup actors by their names. These names are what we have configured in the @Provides
method for each actor.
Now let us write a client for our actors
import ActorUtil._
val injector = Guice.createInjector(new MyModule)
val myActor = injector.getActor("MyActor")
val msg = (myActor ? MyMsg(10, 20)).mapTo[Int]
If I was writing a class which needed the MyActor as a dependency. I could write
@Singleton
class MyClass @Inject()(@Named("MyActor") myactor: ActorRef) {
myactor ! Msg(...)
}
That’s pretty clean because now I can get instances of my actors using Guice mechanisms. This leads to more consistent coding style.
The full code of this example is located here