I am trying to get akka working with a spring app. It's a search app that fits the akka model perfectly. Most of the example online and on typesafe regarding this integration talk about using the akka extension to inject the spring application context. However, they all use ActorSystem.actorOf()
method to create the actors which is known to be expensive operation.
ActorSystem system = ctx.getBean(ActorSystem.class);
system.actorOf(.....)
See - https://github.com/typesafehub/activator-akka-java-spring/blob/master/src/main/java/sample/Main.java
I want to use actors to process every web request that passes authentication. With the above code, I will end up creating root actor for every single request which is not ideal.
Any pointers would be really appreciated.
The below doesn't answer the original question which is about the reducing the number of new actors that need to be created generally when a request comes into to the web app.
To do this with using an Akka router it can be as simple as the following line of code:
getContext().actorOf(SpringExtProvider.get(getContext().system()).props("AnotherActor.").withRouter(new RoundRobinPool(100)), "another");
The Akka docs provide much more detail regarding configuration though so worth taking a look http://doc.akka.io/docs/akka/snapshot/java/routing.html. It may be preferable to define their behaviour via the Akka config file rather than hard-coded into the app. You can call it like:
getContext().actorOf(SpringExtProvider.get(getContext().system()).props("AnotherActor.").withRouter(new FromConfig()), "another");
..and define the type and behviour of the router in your application.conf file.
If you've not already considered it, it's also worth checking out how to make your web rerquests asynchronous. One approach would be to use Spring's DeferredResult and pass an instance of that to your actor and set the result on completion of the search request.
--Update 20/11--
I think why the actor selection isn't working for you is because you are trying to use the bean name, not the actor name as the pacth to the actor selection. When creating the router you don't specify an actor name so it will be given a name internally by Akka, soemthing like "$a".
To illustrate, if you create your actor with:
actorSystem.actorOf(this.get(actorSystem).props(applicationContext, "bean_name"), "actorName");
Then you should be able to perform an actor selection with:
actorSystem.actorSelection("actorName");
Alternatively to create the above router actor once, and then re-use it in every request made to your Spring MVC web service, you could create it in a Spring @Configuration class and expose the ActorRef for it as a bean so you can inject into your Spring @Controller class. The following is a quick example I have created frmo memory so please make sure it is tested/compiles etc.
@Configuration
public class Config {
@Autowired
private ActorSystem actorSystem;
@Bean(name = "routerActorRef")
public ActorRef routerActorRef() {
getContext().actorOf(SpringExtProvider.get(actorSystem).props("AnotherActor").withRouter(new RoundRobinPool(100)), "another");
}
}
You can then inject this into another Spring bean by writing something like:
@Autowired
@Qualifier("routerActorRef")
private ActorRef routerActorRef;
It should be noted that this is only really feasible with top-level actors, it's possible with lower level actors but will become quite tricky to manage efficiently.
-- Original answer --
The example in the Main method you link to is showing how an initial top-level actor is created that will be supervised by the system based "user" actor. It is quite a simple example that demonstrates creating a Spring managed actor named CountingActor with a Spring managed bean named CountingService injected into it.
To take this example further you could define another actor which looks similar to CountingActor e.g.
@Named("AnotherActor")
@Scope("prototype")
class AnotherActor extends UntypedActor {
// the service that will be automatically injected
final AnotherService anotherService;
@Inject
public AnotherActor(@Named("AnotherService") AnotherService anotherService) {
this.anotherService = anotherService;
}
@Override
public void onReceive(Object message) throws Exception {
if (message == "doSomething") {
anotherService.doSomething();
} else {
unhandled(message);
}
}
}
I have assumed that there is another Spring service bean called AnotherService that has a method doSomething() which will be injected when the AnotherActor is created.
Then in CountingActor you can create AnotherActor like so:
@Named("CountingActor")
@Scope("prototype")
class CountingActor extends UntypedActor {
public static class Count {}
public static class Get {}
// the service that will be automatically injected
final CountingService countingService;
@Inject
public CountingActor(@Named("CountingService") CountingService countingService) {
this.countingService = countingService;
}
private int count = 0;
@Override
public void onReceive(Object message) throws Exception {
if (message instanceof Count) {
count = countingService.increment(count);
// Create AnotherActor here as a child of CountingActor by using the CountingActor's context
ActorRef anotherActor = getContext().actorOf(SpringExtProvider.get(system).props("AnotherActor"), "another");
anotherActor.tell("doSomething", getSelf());
} else if (message instanceof Get) {
getSender().tell(count, getSelf());
} else {
unhandled(message);
}
}
}
The key difference between the actor creation here and the one is main is the use of getContext().actorOf(...) instead of system.actorOf(...). Using getContext() causes the new actor to be created as a child of CounterActor, not the top level "user" actor.
There is a good description of this behaviour in the official Akka docs: http://doc.akka.io/docs/akka/snapshot/java/untyped-actors.html#creating-actors-with-props
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With