§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.
import akka.actor._
object HelloActor {
def props = Props[HelloActor]
case class SayHello(name: String)
}
class HelloActor extends Actor {
import HelloActor._
def receive = {
case SayHello(name: String) =>
sender() ! "Hello, " + name
}
}
This actor follows a few Akka conventions:
- The messages it sends/receives, or its protocol, are defined on its companion object
- It also defines a
props
method on its companion object that returns the props for creating it
§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. , like so:
import play.api.mvc._
import akka.actor._
import javax.inject._
import actors.HelloActor
@Singleton
class Application @Inject() (system: ActorSystem) extends Controller {
val helloActor = system.actorOf(HelloActor.props, "hello-actor")
//...
}
The actorOf
method is used to create a new actor. Notice that we’ve declared this controller to be a singleton. This is necessary since we are creating the actor and storing a reference to it, if the controller was not scoped as singleton, this would mean a new actor would be created every time the controller was created, which would ultimate throw an exception because you can’t have two actors in the same system with the same name.
§Asking things of actors
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 Future
, which you can then map to your own result type.
Below is an example of using our HelloActor
with the ask pattern:
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
import akka.pattern.ask
implicit val timeout = 5.seconds
def sayHello(name: String) = Action.async {
(helloActor ? SayHello(name)).mapTo[String].map { message =>
Ok(message)
}
}
A few things to notice:
- The ask pattern needs to be imported, and then this provides a
?
operator on the actor. - The return type of the ask is a
Future[Any]
, usually the first thing you will want to do after asking actor is map that to the type you are expecting, using themapTo
method. - An implicit timeout is needed in scope - the ask pattern must have a timeout. If the actor takes longer than that to respond, the returned future will be completed with a timeout error.
§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.
§Scheduling asynchronous tasks
You can schedule sending messages to actors and executing tasks (functions or Runnable
). 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 300 microseconds:
import scala.concurrent.duration._
val cancellable = system.scheduler.schedule(
0.microseconds, 300.microseconds, testActor, "tick")
Note: This example uses implicit conversions defined in
scala.concurrent.duration
to convert numbers toDuration
objects with various time units.
Similarly, to run a block of code 10 milliseconds from now:
import play.api.libs.concurrent.Execution.Implicits.defaultContext
system.scheduler.scheduleOnce(10.milliseconds) {
file.delete()
}
Next: Internationalization