Friday, February 3, 2012

Akka Untyped Actors & Google Guice

I started using Akka Actors in my Scala project and wanted to make them play nice with Google Guice.

Akka Actors are STRICTLY (I don't think you understand how strict) enforced to be instantiated in a certain way.  So I went on a trek to do this.

First off, Akka Actors are wrapped in an ActorRef. This ref has no reference to the underlying Actor type. An ActorRef is an ActorRef is an ActorRef. So to differentiate them you must use annotations. Not wanting to make a new annotation for each I went with the provided @Named annotation. This leads to a not so terrible injection of:

class WeaveCompiler @Inject() ( @Named("WeaveActor") val weaveActor:ActorRef ) {

That's all fine and dandy, and satisfies the first half of the binding:

bind(classOf[ActorRef].annotatedWith( Names.named("WeaveActor") )

But now we need our ActorRef. We can't let Guice do it for us since we have an Actor and it needs an ActorRef. Time for a provider. I didn't want to have to make a provider for every single Actor so I made it generic. Java type erasure quickly bites us in the ass but there are ways around that there are over my head. In short, the following works:

.toProvider( new TypeLiteral[ActorProvider[WeaveActor]]() {} )

So TypeLiteral is provided by Guice and takes care of that type erasure headache. The ActorProvider is my own special glue that I will provide below and WeaveActor is my Actor that extends akka.actor.Actor

So here is my glue:

import com.google.inject._
import akka.actor._

class ActorProvider[T <: Actor] @Inject() ( val m:TypeLiteral[T], val injector:Injector ) extends Provider[ActorRef] {
  def get = {
    Actor.actorOf( injector.getInstance( Key.get( m ) ) ).start
  }
}

Because of how akka actors work the Actor object MUST be created inside a call to actorOf. Fail to do that and you get a horrible exception.  Thus the provider gets the TypeLiteral for the actor injected with the Injector from Guice. Then uses those to get an instance of the Actor from the Injector inside the call to actorOf. Then it starts the actor and returns the ActorRef.

This solution seems simple, but I spent the better part of 4 hours to figure it out.  There is probably still something missing (I haven't checked what happens if you inject the same actor in multiple places) but that can be an exercise for the reader. If I find any problems I'll make another post about it.

Hope this helps!

3 comments:

Alois Cochard said...

Yo mate, well done.

Just to let you know that a scala project exist for integration with Guice:
https://github.com/benlings/scala-guice

Be using it you can convert TypeLiteral to the more scala-ish conterpart: Manifest

Anyway I'll try to setup an example as soons as possible of integration of Akka with my IoC (http://github.com/aloiscochard/sindi) which is even more scala-ish ;)

May The Force be with you!

NEM said...

Is this still relevant for Akka 2.0 or 2.0.1? The creation has changed using ActorContext or ActorSystem. Secondly they are forcing Prop[] now.

Is there a way to do this with these changes? I am trying to figure this out now.

Thanks!

Thomas Suckow said...

I'm not sure about the strictness, but I did use a similar method for instantiating the actors.

I created this provider to make it easier:

https://github.com/Deathbobomega/Weave/blob/7acf8b01fae970e1d172f05ab622e05b32196356/core-api/src/main/scala/net/codingwell/weave/ActorProvider.scala

Then you can instantiate it with

bind(classOf[ActorRef])
.annotatedWith( Names.named("WeaveActor") )
.toProvider( new TypeLiteral[ActorProvider[WeaveActor]]() {} )

To use a more scala friendly syntax:
.toProvider[ActorProvider[WeaveActor]]

I recommend using my port of Scala-Guice
http://ci.codingwell.net/job/Scala-Guice/