I am a little confused with the play documentation https://www.playframework.com/documentation/2.5.x/ScalaAkka
Looking at the examples, one can start an actor from a controller:
import play.api.mvc._
import akka.actor._
import javax.inject._
#Singleton
class Application #Inject() (system: ActorSystem) extends Controller {
val actor = system.actorOf(Props(classOf[AnActor], "anActor")
//...
}
Or one can rely on Guice to instantiate that actor
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
import actors.ConfiguredActor
class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[AnActor]("anActor")
}
}
When the actor is instantiated by Guice, it is possible to inject a dependency into it
import akka.actor._
import javax.inject._
import play.api.Configuration
class AnActor #Inject() (configuration: Configuration) extends Actor {
//...
}
However, starting that actor from a controller raises an
[IllegalArgumentException: no matching constructor found on class AnActor for arguments []]
Is there a way to inject a service into a non-Guice-instantiated actor?
You could use bindActorFactory and then use a factory for creating actors in controllers.
Bind an actor factory. This is useful for when you want to have child actors injected, and want to pass parameters into them, as well as have Guice provide some of the parameters.
You nead a Factory in your Actor's companion object.
import akka.actor._
object AnActor {
trait Factory {
def apply(): Actor
}
}
class AnActor #Inject() (configuration: Configuration) extends Actor {
def receive = ???
}
You use bindActorFactory in your Module
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
import actors._
class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActorFactory[AnActor, AnActor.Factory]
}
}
Then, you could instantiate your actors from your controller using the actor system (so they will be childs of /user actor) and guice will do its magic.
class LocationsController #Inject()(actorSystem: ActorSystem){
def injectedChild2(create: => Actor, name: String, props: Props => Props = identity)(implicit system: ActorSystem): ActorRef = {
system.actorOf(props(Props(create)), name)
}
val actor1: ActorRef = injectedChild2(childFactory(),"anActor1")(actorSystem)
val actor2: ActorRef = injectedChild2(childFactory(),"anActor2")(actorSystem)
}
You could add parameters to the Factory.apply if you like to pass parameters that aren't injected by guice, in this case you need to annotate this parameters with #Assisted
object AnActor {
trait Factory {
def apply(someValue: String): Actor
}
}
class AnActor #Inject() (configuration: Configuration,#Assisted someValue: String) extends Actor {
def receive = ???
}
class LocationsController #Inject()(actorSystem: ActorSystem){
def injectedChild2(create: => Actor, name: String, props: Props => Props = identity)(implicit system: ActorSystem): ActorRef = {
system.actorOf(props(Props(create)), name)
}
val actor1: ActorRef = injectedChild2(childFactory("value fom actor 1"),"anActor1")(actorSystem)
val actor2: ActorRef = injectedChild2(childFactory("value from actor 2"),"anActor2")(actorSystem)
}
The example and the method injectedChild2 are taken from the original Play example but modified to create actors from controllers.
UPDATE:
check this answer to know why you should avoid create actors from a controller, in the words of James Roper.
Related
I have a WebSocket controller which creates per connection actor handler:
class WebSocketController #Inject()(cc: ControllerComponents)(implicit exc: ExecutionContext) {
def socket: WebSocket = WebSocket.accept[JsValue, JsValue] { request =>
ActorFlow.actorRef { out => // Flow that is handled by an actor from 'out' ref
WebSocketActor.props(out) // Create an actor for new connected WebSocket
}
}
}
And inside the actor handler I need to work with ReactiveMongo:
trait ModelDAO extends MongoController with ReactiveMongoComponents {
val collectionName: String
...
}
class UsersCollection #Inject()(val cc: ControllerComponents,
val reactiveMongoApi: ReactiveMongoApi,
val executionContext: ExecutionContext,
val materializer: Materializer)
extends AbstractController(cc) with ModelDAO {
val collectionName: String = "users"
}
So, the usual way is to #Inject() UsersCollection in the target class. But I can't do something like:
class WebSocketActor #Inject()(out: ActorRef, users: UsersCollection) extends Actor { ... }
Because instances of actor creates inside WebSocketActor companion object:
object WebSocketActor {
def props(out: ActorRef) = Props(new WebSocketActor(out))
}
How do I use UsersCollection inside the WebSocketActor?
You can create actor who's dependencies will be injected automatically by Play. No problem. (https://www.playframework.com/documentation/2.6.x/ScalaAkka)
But in case of web sockets, Props of the actor is expected, but not Actor (or ActorRef) itself.
ActorFlow.actorRef { out => // Flow that is handled by an actor from 'out' ref
WebSocketActor.props(out) // <- ACTOR IS NOT CREATED HERE, WE RETURN PROPS
}
So there is no way to do it automatically in this case (At least I didn't find it).
What you can do is to pass UsersCollection manually.
class WebSocketController #Inject()(cc: ControllerComponents, usersCollection: UsersCollection)(implicit exc: ExecutionContext) {
def socket: WebSocket = WebSocket.accept[JsValue, JsValue] { request =>
ActorFlow.actorRef { out => // Flow that is handled by an actor from 'out' ref
WebSocketActor.props(out, usersCollection) //<- ACTOR IS NOT CREATED HERE, WE RETURN PROPS
}
}
}
Noticed that I injected UsersCollection into WebSocketController and pass it to props.
Simple and I do not see any disadvantage.
I have an event handler module inside my play app that basically instantiates all actors upon application startup. event handlers are all actors so you could do it like this:
class EventHandlerBootstrap #Inject() (system: ActorSystem, app: Application) {
EventHandlerBootstrap.handlers.foreach {
case (h, n) => system.actorOf(Props(app.injector.instanceOf(h)), n)
}
}
//These Class[_ <: EventHandler] are classes of user defined actors each with their own
// dependencies which guice will take care of automattically.
object EventHandlerBootstrap {
val handlers: Map[Class[_ <: EventHandler], String] = Map(
classOf[UserRegisteredHandler] -> "user-registered-handler",
classOf[CustomerOrderCreatedHandler] -> "customer-order-created-handler",
classOf[DisputeClosedHandler] -> "dispute-closed-handler",
classOf[Throttle] -> "throttle"
)
}
and inside the module i run the bootstrapper like this:
class EventModule extends ScalaModule with AkkaGuiceSupport {
override def configure(): Unit = {
bind[EventHandlerBootstrap].asEagerSingleton()
}
}
if you're ofcourse not eager to blindly follow my recipe you can still take out the fact that injecting your actors and their dependencies is fully supported by guice as shown above. Hope It'll help
I want to inject two actors into a Play controller via DI. Injecting one actors works absolutely fine and I can send message to this actor without any problems. However, when injecting a second actor and sending a message, I get the following compilation error:
play.sbt.PlayExceptions$CompilationException: Compilation error[ambiguous implicit values:
both value fooSupervisor in class MyController of type akka.actor.ActorRef
and value barSupervisor in class MyController of type akka.actor.ActorRef
match expected type akka.actor.ActorRef]
My controller looks like this:
class MyController #Inject()(
implicit system: ActorSystem,
materializer: Materializer,
#Named("FooSupervisor") fooSupervisor: ActorRef,
#Named("BarSupervisor") barSupervisor: ActorRef
) extends Controller {
}
And the module looks as follows:
class Module extends AbstractModule with ScalaModule with AkkaGuiceSupport {
def configure() {
// ....
bindActor[FooSupervisor]("FooSupervisor")
bindActor[BarSupervisor]("BarSupervisor")
()
}
}
How can I solve this issue?
You define the two actors as implicit parameters. Change the signature like so:
class MyController #Inject()(
#Named("FooSupervisor") fooSupervisor: ActorRef,
#Named("BarSupervisor") barSupervisor: ActorRef
)(implicit system: ActorSystem, materializer: Materializer
) extends Controller {
I tried injecting the actorRef into the controller from a provider.
import javax.inject._
import akka.actor.ActorRef
import play.api.mvc._
#Singleton
class HomeController #Inject()(#Named("actor1") val actor1: ActorRef, #Named("actor2") val actor2: ActorRef) extends Controller {
def index = Action {
actor1 ! "hello actor1"
actor2 ! "hello actor2"
Ok("Hello")
}
}
The Module should bind the actorRef.
import javax.inject.{Inject, Provider}
import com.google.inject.AbstractModule
import net.codingwell.scalaguice.ScalaModule
import actors.{Actor1, Actor2}
import akka.actor.{ActorRef, ActorSystem, Props}
class Module extends AbstractModule with ScalaModule{
override def configure() = {
bind[ActorRef].annotatedWithName("actor1").toProvider[Actor1Provider]
bind[ActorRef].annotatedWithName("actor2").toProvider[Actor2Provider]
}
}
class Actor1Provider #Inject() (system: ActorSystem) extends Provider[ActorRef]{
override def get(): ActorRef = system.actorOf(Props[Actor1])
}
class Actor2Provider #Inject() (system: ActorSystem) extends Provider[ActorRef]{
override def get(): ActorRef = system.actorOf(Props[Actor2])
}
If you need a materializer and actorSystem please pass them as implicit parameters. Since we have injected the actorRef itself, we might not need a actorSystem to interact with the actors. But depending upon what else you do inside the controller - if you need things like materializer or actorSystem making them implicit is better.
I'm not able to figure out how to inject a Service into an Actor.
I tried out several approaches and think this one should fit best.
I think the major problem is that I provide the websocket to the succeeding actor as an parameter. When I pull the Inject up to the class signature compilation doesn't perform because of signature conflict.
I tried adding a currying implicit class declaration to inject the service, but this didn't work out either.
What shall I do to get it right?
Running this code results in a NullPointerException.
package actors
import akka.actor._
import com.google.inject.Inject
import play.api.libs.json._
import models._
import services.WeatherService
/**
* Created by jbc on 28.12.16.
*/
class WeatherWSActor (out: ActorRef) extends Actor {
#Inject() val weatherService: WeatherService = null
def handleMessage(msg: JsValue): JsValue = {
val req = msg.as[Request]
req match {
case Request("daily") => Json.toJson(weatherService.getFourDays())
case Request("next") => Json.toJson(weatherService.getNextHour())
}
}
override def receive: Receive = {
case msg: JsValue => out ! handleMessage(msg)
case _ => out ! "Bad Request"
}
#scala.throws[Exception](classOf[Exception])
override def postStop(): Unit = {
}
}
object WeatherWSActor {
def props(out: ActorRef) = Props(new WeatherWSActor(out))
}
Controller Code:
class WeatherWSController #Inject() (implicit system: ActorSystem, materializer: Materializer) extends Controller {
def socket = WebSocket.accept[JsValue, JsValue] { request =>
ActorFlow.actorRef(out => WeatherWSActor.props(out));
}
}
The service is set up with this Module Code
class Module extends AbstractModule {
override def configure(): Unit = {
bind(classOf[WeatherService]).to(classOf[WeatherServiceImpl]).asEagerSingleton()
}
}
This is covered in the documentation, but here's a summary.
If you want to inject your actors, you need to change a couple of things. First off, the injection point is the constructor, so
class WeatherWSActor (out: ActorRef) extends Actor {
#Inject() val weatherService: WeatherService = null
// ...
}
should become
class WeatherWSActor #Inject() (out: ActorRef,
weatherService: WeatherService) extends Actor {
// ...
}
In fact, assigning null to a Scala val is also going to give you issues.
Secondly, declare a binding for the actor instead of creating it yourself.
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
import actors.WeatherWSActor
class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[WeatherWSActor]("weather-actor")
}
}
On a side note, you'll also need to create a named binding for out and change the definition of WeatherWSActor to reflect this:
class WeatherWSActor #Inject() (#javax.inject.Named("out-actor") out: ActorRef,
weatherService: WeatherService) extends Actor {
// ...
}
out-actor is whichever class you have set up in the module, e.g.
class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[WeatherWSActor]("weather-actor")
bindActor[SomeOutActor]("out-actor")
}
}
But...
Since you're getting out when a web socket is created, you're (presumably) going to have multiple WeatherWSActor instances. This is going to effect how you handle the dependency injection, so it make more sense to not inject outActor (and hence not create a binding for it), but rather have a singleton WeatherWSActor that broadcasts to all instances of OutActor addressed at a certain point in the actor system.
I would like to be able to create multiple instances of the same parent actor, but with different child actors. I assume this has to be possible with Guice, but I haven't found the solution.
Here is what I have in mind ~
Controller:
class Application #Inject()(#Named(ParentActor.parentActor1) parentActor1: ActorRef,
#Named(ParentActor.parentActor2) parentActor2: ActorRef)
extends Controller {
def index = Action {
parentActor1 ! "Message"
parentActor2 ! "Message"
Ok()
}
}
Parent Actor:
object ParentActor {
final val parentActor1 = "parentActor1"
final val parentActor2 = "parentActor2"
}
class ParentActor #Inject() (childActor: ActorRef) extends Actor {
def receive = {
case "Message" =>
println(s"ParentActor ${self.path} received message...")
childActor ! "Message"
}
}
Child Actor A:
class ChildActorA extends Actor {
def receive = {
case "Message" =>
println("ChildActorA received message...")
}
}
Child Actor B:
class ChildActorB extends Actor {
def receive = {
case "Message" =>
println("ChildActorB received message...")
}
}
Module:
class Modules extends AbstractModule with AkkaGuiceSupport {
override def configure() = {
bindActor[ParentActor](ParentActor.parentActor1)
bindActor[ParentActor](ParentActor.parentActor2)
}
}
What if I wanted "parentActor1" to have have its "childActor" ref point to an instance of ChildActorA and "parentActor2" to have its "childActor" ref point to an instance of ChildActorB? Is this possible to achieve with Guice?
I'm using some code based on https://github.com/rocketraman/activator-akka-scala-guice to accomplish something similar
I'm not using Play, so I have to initialize Guice and bootstrap the actor system
import akka.actor._
import javax.inject.{Inject, Provider, Singleton}
import com.google.inject.AbstractModule
import net.codingwell.scalaguice.InjectorExtensions._
import com.google.inject.Guice
import com.google.inject.Injector
import scala.concurrent.Await
import scala.concurrent.duration.Duration
object Bootstrap extends App {
val injector = Guice.createInjector(
new AkkaModule(),
new ServiceModule()
)
implicit val system = injector.instance[ActorSystem]
val parentActor1 = system.actorOf(ParentActor.props(ChildActorA.name))
val parentActor2 = system.actorOf(ParentActor.props(ChildActorB.name))
parentActor1 ! "Message"
parentActor2 ! "Message"
system.terminate()
Await.result(system.whenTerminated, Duration.Inf)
}
To initialize Guice there are two classes/objects:
One to initialize the extension and inject the actor system where required
import akka.actor.ActorSystem
import AkkaModule.ActorSystemProvider
import com.google.inject.{AbstractModule, Injector, Provider}
import com.typesafe.config.Config
import net.codingwell.scalaguice.ScalaModule
import javax.inject.Inject
object AkkaModule {
class ActorSystemProvider #Inject() (val injector: Injector) extends Provider[ActorSystem] {
override def get() = {
val system = ActorSystem("actor-system")
GuiceAkkaExtension(system).initialize(injector)
system
}
}
}
class AkkaModule extends AbstractModule with ScalaModule {
override def configure() {
bind[ActorSystem].toProvider[ActorSystemProvider].asEagerSingleton()
}
}
another one to create the providers for the children
import javax.inject.Inject
import akka.actor.{Actor, ActorRef, ActorSystem}
import com.google.inject.name.{Named, Names}
import com.google.inject.{AbstractModule, Provides, Singleton}
import net.codingwell.scalaguice.ScalaModule
class ServiceModule extends AbstractModule with ScalaModule with GuiceAkkaActorRefProvider {
override def configure() {
bind[Actor].annotatedWith(Names.named(ChildActorA.name)).to[ChildActorA]
bind[Actor].annotatedWith(Names.named(ChildActorB.name)).to[ChildActorB]
}
#Provides
#Named(ChildActorA.name)
def provideChildActorARef(#Inject() system: ActorSystem): ActorRef = provideActorRef(system, ChildActorA.name)
#Provides
#Named(ChildActorB.name)
def provideChildActorBRef(#Inject() system: ActorSystem): ActorRef = provideActorRef(system, ChildActorB.name)
}
The extension
import akka.actor._
import com.google.inject.Injector
class GuiceAkkaExtensionImpl extends Extension {
private var injector: Injector = _
def initialize(injector: Injector) {
this.injector = injector
}
def props(actorName: String) = Props(classOf[GuiceActorProducer], injector, actorName)
}
object GuiceAkkaExtension extends ExtensionId[GuiceAkkaExtensionImpl] with ExtensionIdProvider {
override def lookup() = GuiceAkkaExtension
override def createExtension(system: ExtendedActorSystem) = new GuiceAkkaExtensionImpl
override def get(system: ActorSystem): GuiceAkkaExtensionImpl = super.get(system)
}
trait NamedActor {
def name: String
}
trait GuiceAkkaActorRefProvider {
def propsFor(system: ActorSystem, name: String) = GuiceAkkaExtension(system).props(name)
def provideActorRef(system: ActorSystem, name: String): ActorRef = system.actorOf(propsFor(system, name))
}
producer
import akka.actor.{IndirectActorProducer, Actor}
import com.google.inject.name.Names
import com.google.inject.{Key, Injector}
class GuiceActorProducer(val injector: Injector, val actorName: String) extends IndirectActorProducer {
override def actorClass = classOf[Actor]
override def produce() = injector.getBinding(Key.get(classOf[Actor], Names.named(actorName))).getProvider.get()
}
and your actors
import javax.inject.Inject
import akka.actor._
object ParentActor {
def props(childName: String)(implicit #Inject() system: ActorSystem) = Props(classOf[ParentActor],system.actorOf(GuiceAkkaExtension(system).props(childName)))
}
class ParentActor (childActor: ActorRef) extends Actor {
def receive = {
case "Message" =>
println(s"ParentActor ${self.path} received message...")
childActor ! "Message"
}
}
object ChildActorA extends NamedActor{
override final val name = "ChildActorA"
def props() = Props(classOf[ChildActorA])
}
class ChildActorA extends Actor {
def receive = {
case "Message" =>
println("ChildActorA received message...")
}
}
object ChildActorB extends NamedActor{
override final val name = "ChildActorB"
def props() = Props(classOf[ChildActorB])
}
class ChildActorB extends Actor {
def receive = {
case "Message" =>
println("ChildActorB received message...")
}
}
the output from sbt
> run
[info] Running Bootstrap
ParentActor akka://actor-system/user/$b received message...
ParentActor akka://actor-system/user/$d received message...
ChildActorB received message...
ChildActorA received message...
[success] Total time: 1 s, completed Jun 14, 2016 1:23:59 AM
You have to explicitly name the children,
It's not the purest or most elegant answer, and I'm sure the code can be optimized, but it allows you to create instances of the same parent with different children.
I'm thinking that you can also use BindingAnnotations
I have an actor
import akka.actor.{Props, Actor}
import scala.reflect.ClassTag
class MyActor1[T<: Actor: ClassTag] extends Actor {
//....
}
import akka.actor.{Props, Actor}
import scala.reflect.ClassTag
class MyActor2[T <: Actor: ClassTag] extends Actor {
def receive = {
case Start =>
val actor1 = context actorOf Props[MyActor3] //MyActor3 is another actor
actor1 ! Get
case Result(ids: List[Int]) =>
val myActor1List = ids map { new MyActor1[T](_) }
myActor1List foreach { _ ! SomeMessage } // error: "!" is not a member of MyActor1[T]
}
}
The error is "!" is not a member of MyActor1[T]
That is because ! is a member of ActorRef, not Actor. You should always access actors via ActorRefs, not via instances of your classes that extend Actor directly.
Your myActor1List contains instances of class MyActor1, instead of ActorRefs. Create your actors by calling context.actorOf[MyActor1] instead of instantiating MyActor1 objects directly.
You're already doing it in your own code. Notice the difference between:
// Create an actor and get an ActorRef
val actor1 = context actorOf Props[MyActor3]
// Create instances of your actor class directly
val myActor1List = ids map { new MyActor1[T](_) }