1

I am trying to refactor an Akka application from a single tier to multi-tier structure with akka clustering (http://doc.akka.io/docs/akka/2.3.9/scala/cluster-usage.html). Within the application are some actors that have a parameterized default constructor, e.g.

class MyActor(someParam: boolean = true) extends Actor {
    def receive = { /* message handling here... */ }
}

I am now trying to create a generic router actor that will route to a node with a specific node. The cluster configuration is working based of the example on the akka site, and I can create a router in 1 role that routes to the actor in another role:

class MyActorProxy extends Actor {
  val workerRouter = context.actorOf(FromConfig.props(Props(classOf[MyActor]), name = "workerRouter")
  def receive = {
    case msg: Any =>
      val currentSender = sender()
      workerRouter.tell(msg, currentSender)
  }
}

This method works as needed. However, I need to go and create a handful of proxy classes with the same functionality, and it's painful. I'm trying to make it work better, and limited scala experience is making it difficult. Here's what I am trying to do:

abstract class RouterProxy[T <: Actor](routerPath: String) extends Actor {
  val router = context.actorOf(FromConfig.props(Props(classOf[T])),name=routerPath)
  def receive = {
     case msg: Any => {
       val currentSender = sender()
       router.tell(msg,currentSender)
     }
  } 
}

class MyActorProxy extends RouterProxy[MyActor](routerPath = "myActorRouter")

Have come up with this by reading through some scala docs and through stackoverflow, but can't seem to get it right - this approach doesn't let me create the props for the actor. I've seen How to instantiate an instance of type represented by type parameter in Scala and I think that the type information is being lost (type erasure).

How would I go about passing an actor class to the router proxy to allow for router initialization?

This is the error I get during compilation, while initializing the router:

class type required but T found

edit

Background for using a generic proxy router

The reason for this approach is to refactor the application for both single tier and multi-tier architecture. When running multi-tier, I want to skip initializing any "heavy" actors. Given I have the following config on a frontend node:

akka.actor.deployment {
  /workerRouter {
    routee.paths = ["/user/myActor"]
    cluster.use-role = backend
  }
}

akka.cluster.roles = [frontend]

and the backend node has roles of [backend], then when I start up frontend nodes, MyActor will not be initialized. MyActor is an example of 1 of many actors that are supervisors / managers. Each 1 of them might initialize a few other actors in turn (sometimes with routers). What I'm trying to do is refactor the application so I can run lightweight frontend nodes in a different tier to resource heavy backend nodes if I need to, but also still have the ability to run all on a single node. By this approach, I can add in initialization for heavy managers into my application bootstrap and add a role and it becomes a multifunctional app without needing recoding.

edit 2 I am able to get the application to work as expected if the class constructor doesn't take any arguments.

MyActor extends Actor { /* ... */ }

And then in my abstract class:

abstract class RouterProxy[T <: Actor](routerPath: String) extends Actor {
  val router = context.actorOf(FromConfig.props(Props[T]),name = routerPath)
}

Props[MyActor] works fine for a parameterless actor, but Props(classOf[T]) looses the type.

Community
  • 1
  • 1
Brett
  • 4,747
  • 4
  • 31
  • 52

1 Answers1

1

You don't actually have to define another class for the router, you can do it all through your configuration file:

val myActorRouter = system.actorOf(Props[MyActor]
      .withRouter(FromConfig())
      ,"workerRouter")

Where "workerRouter" is defined in your application.conf file like this:

akka.actor.deployment {
  /workerRouter {
    router = round-robin
    nr-of-instances = 5
  }
}

You can also configure it all through code if you have a pretty simple configuration:

val myActorRouter = system.actorOf(Props[MyActor].withRouter(
  RoundRobinRouter(nrOfInstances = 5)))

Either way you end up with myActorRouter as an actor ref you can send messages to. In the background the Akka system has created a pool of MyActor actors and forwards the messages to them. The router takes itself out of the response so you don't need to spoof the sender like you've done: when a routee sends a message to sender it goes directly back to the actor that sent the message to the router.

Gangstead
  • 3,907
  • 18
  • 33
  • Thanks for the feedback @gangstead - you are right, I can take that approach, but I want to be able to run a node without all the heavy components of `MyActor` being initialized (when using a multi-tier application). I guess the question is not about why I'm building in a bit of redundancy, but more of a general Scala programming question: **How can I get around type erasure and preserve sub-class typing.** – Brett Mar 04 '15 at 12:33
  • Have you seen this?http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html – almendar Mar 04 '15 at 13:24