§Integrating with Akka
Akka uses the Actor Model to raise the abstraction level and provide a better platform to build correct concurrent and scalable applications. For fault-tolerance it adopts the ‘Let it crash’ model, which has been used with great success in the telecoms industry to build applications that self-heal - systems that never stop. Actors also provide the abstraction for transparent distribution and the basis for truly scalable and fault-tolerant applications.
§The application actor system
Akka can work with several containers called actor systems. An actor system manages the resources it is configured to use in order to run the actors which it contains.
A Play application defines a special actor system to be used by the application. This actor system follows the application life-cycle and restarts automatically when the application restarts.
Note: Nothing prevents you from using another actor system from within a Play application. The provided default is convenient if you only need to start a few actors without bothering to set-up your own actor system.
§Writing actors
To start using Akka, you need to write an actor. Below is a simple actor that simply says hello to whoever asks it to.
package actors;
import akka.actor.*;
import actors.HelloActorProtocol.*;
public class HelloActor extends UntypedActor {
public static Props props = Props.create(HelloActor.class);
public void onReceive(Object msg) throws Exception {
if (msg instanceof SayHello) {
sender().tell("Hello, " + ((SayHello) msg).name, self());
}
}
}
Notice here that the HelloActor
defines a static method called props
, this returns a Props
object that describes how to create the actor. This is a good Akka convention, to separate the instantiation logic from the code that creates the actor.
Another best practice shown here is that the messages that HelloActor
sends and receives are defined as static inner classes of another class called HelloActorProtocol
:
package actors;
public class HelloActorProtocol {
public static class SayHello {
public final String name;
public SayHello(String name) {
this.name = name;
}
}
}
§Creating and using actors
To create and/or use an actor, you need an ActorSystem
. This can be obtained by declaring a dependency on an ActorSystem, then you can use the actorOf
method to create a new actor.
The most basic thing that you can do with an actor is send it a message. When you send a message to an actor, there is no response, it’s fire and forget. This is also known as the tell pattern.
In a web application however, the tell pattern is often not useful, since HTTP is a protocol that has requests and responses. In this case, it is much more likely that you will want to use the ask pattern. The ask pattern returns a Scala Future
, which you can then wrap in a Play Promise
, and then map to your own result type.
Below is an example of using our HelloActor
with the ask pattern:
- Java
-
import akka.actor.*; import play.mvc.*; import play.libs.F.*; import javax.inject.*; import static akka.pattern.Patterns.ask; @Singleton public class Application extends Controller { final ActorRef helloActor; @Inject public Application(ActorSystem system) { helloActor = system.actorOf(HelloActor.props); } public Promise<Result> sayHello(String name) { return Promise.wrap(ask(helloActor, new SayHello(name), 1000)).map( new Function<Object, Result>() { public Result apply(Object response) { return ok((String) response); } } ); } }
- Java 8
-
import akka.actor.*; import play.mvc.*; import play.libs.F.*; import javax.inject.*; import static akka.pattern.Patterns.ask; @Singleton public class Application extends Controller { final ActorRef helloActor; @Inject public Application(ActorSystem system) { helloActor = system.actorOf(HelloActor.props); } public Promise<Result> sayHello(String name) { return Promise.wrap(ask(helloActor, new SayHello(name), 1000)) .map(response -> ok((String) response)); } }
A few things to notice:
- The ask pattern needs to be imported, it’s often most convenient to static import the
ask
method. - The returned future is wrapped in a
Promise
. The resulting promise is aPromise<Object>
, so when you access its value, you need to cast it to the type you are expecting back from the actor. - The ask pattern requires a timeout, we have supplied 1000 milliseconds. If the actor takes longer than that to respond, the returned promise will be completed with a timeout error.
- Since we’re creating the actor in the constructor, we need to scope our controller as
Singleton
, so that a new actor isn’t created every time this controller is used.
§Configuration
The default actor system configuration is read from the Play application configuration file. For example, to configure the default dispatcher of the application actor system, add these lines to the conf/application.conf
file:
akka.default-dispatcher.fork-join-executor.pool-size-max = 64
akka.actor.debug.receive = on
Note: You can also configure any other actor system from the same file, just provide a top configuration key.
For Akka logging configuration, see configuring logging.
By default the name of the ActorSystem
is application
. You can change this via an entry in the conf/application.conf
:
play.modules.akka.actor-system = "custom-name"
Note: This feature is useful if you want to put your play application ActorSystem in an akka cluster.
§Executing a block of code asynchronously
A common use case within Akka is to have some computation performed concurrently without needing the extra utility of an Actor. If you find yourself creating a pool of Actors for the sole reason of performing a calculation in parallel, there is an easier (and faster) way:
- Java
-
import play.libs.F.*; import play.mvc.*; import java.util.concurrent.Callable; import static play.libs.F.Promise.promise; public class Application extends Controller { public Promise<Result> index() { return promise(new Function0<Integer>() { public Integer apply() { return longComputation(); } }).map(new Function<Integer,Result>() { public Result apply(Integer i) { return ok("Got " + i); } }); } }
- Java 8
-
import play.libs.F.Promise; import play.mvc.*; import static play.libs.F.Promise.promise; public class Application extends Controller { public Promise<Result> index() { return promise(() -> longComputation()) .map((Integer i) -> ok("Got " + i)); } }
§Scheduling asynchronous tasks
You can schedule sending messages to actors and executing tasks (functions or Runnable
instances). You will get a Cancellable
back that you can call cancel
on to cancel the execution of the scheduled operation.
For example, to send a message to the testActor
every 30 minutes:
system.scheduler().schedule(
Duration.create(0, TimeUnit.MILLISECONDS), //Initial delay 0 milliseconds
Duration.create(30, TimeUnit.MINUTES), //Frequency 30 minutes
testActor,
"tick",
system.dispatcher(),
null
);
Alternatively, to run a block of code ten milliseconds from now:
- Java
-
system.scheduler().scheduleOnce( Duration.create(10, TimeUnit.MILLISECONDS), new Runnable() { public void run() { file.delete(); } }, system.dispatcher() );
- Java 8
-
system.scheduler().scheduleOnce( Duration.create(10, TimeUnit.MILLISECONDS), () -> file.delete(), system.dispatcher() );
Next: Internationalization