Play Framework, Akka, Guice : How to create multip

2019-08-23 00:28发布

There is an example in play framework doc (https://www.playframework.com/documentation/2.6.x/ScalaAkka) that explains how to inject actor (which has dependencies to be injected) into controller:

@Singleton
class Application @Inject() 
(@Named("configured-actor") configuredActor: ActorRef)
(implicit ec: ExecutionContext) extends Controller {

      implicit val timeout: Timeout = 5.seconds

      def getConfig = Action.async {
        (configuredActor ? GetConfig).mapTo[String].map { message =>
          Ok(message)
        }
      }
    }

But as I understand, it creates a single instance of the actor (e.g., singleton).

I need to create multiple instances of configuredActor inside the controller.

There is also an example which demonstrates how to create child actors (which has dependencies to be injected) instances from parent actor

object ParentActor {
  case class GetChild(key: String)
}

class ParentActor @Inject() (
    childFactory: ConfiguredChildActor.Factory
) extends Actor with InjectedActorSupport {
  import ParentActor._

  def receive = {
    case GetChild(key: String) =>
      val child: ActorRef = injectedChild(childFactory(key), key)
      sender() ! child
  }
}

I've tried to apply this technique inside controller, but injectedChild(childFactory(key), key) requires (implicitly) actor context

def injectedChild(create: => Actor, name: String, props: Props => Props = identity)(implicit context: ActorContext): ActorRef = ...

But I need to create this actor from ActorSysem (e.g. /user).

(I thought to get the context of /user actor, but how? is that correct?)

What is a proper way to create multiple instances of an actor with Guice outside parent actor (for example, inside controller)?

1条回答
爷的心禁止访问
2楼-- · 2019-08-23 00:58

I don't think there is a way of obtaining the ActorContext from outside of an actor, but if you like to create child actors from the /user actor you could do directly system.actorOf.

The problem is like you said, that InjectedActorSupport needs an ActorContext. But if you look the trait, it doesn't do anything fancy, just

  def injectedChild(create: => Actor, name: String, props: Props => Props = identity)(implicit context: ActorContext): ActorRef = {
    context.actorOf(props(Props(create)), name)
  }

So, the solution is to extend the trait and add a method that receives the ActorSystem instead of the ActorContext.

  def injectedChild2(create: => Actor, name: String, props: Props => Props = identity)(implicit system: ActorSystem): ActorRef = {
    system.actorOf(props(Props(create)), name)
  }

In fact, you could even add the method to the controller directly but it is not convenient if you want to do this in multiples controllers.

UPDATE:

although this is technically correct I assumed (correctly) that the lack of a method that receives an ActorSystem was intended.

So, I've asked to the play dev team and James Roper has answered:

The reason for this is that it's generally not good practice to create many actors directly from outside the actor system, because you can't supervise them - that is, you can't say what should happen when they crash (should they be stopped, restarted, escalated etc?). In all the scenarios where I've wanted to create an actor from a controller, it's always ended up being a better design for me to not do that, rather, to send a message to another manager actor to create the child actor. When you do this, it gives you a lot more flexibility in future, for example, lets say in future you have a requirement that when a user resubmits the same operation, it shouldn't start a new actor but should connect to the existing actor. If you have a manager actor, that's very easy to do, you can atomically check if the manager actor has a child for that operation already, and create a new child if necessary. If not, it's not possible. And then in future you move to a multi node setup, and you might decide to shard your actors across a cluster - again, very easy if you were using a manager actor because all you need to do is replace the manager actor with cluster sharding, or put a clustered consistent hashing router in front of your manager, but if creating actors directly from the controller, not possible.

查看更多
登录 后发表回答