§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.
§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 akka.japi.*;
import actors.HelloActorProtocol.*;
public class HelloActor extends AbstractActor {
public static Props getProps() {
return Props.create(HelloActor.class);
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
SayHello.class,
hello -> {
String reply = "Hello, " + hello.name;
sender().tell(reply, self());
})
.build();
}
}
Notice here that the HelloActor
defines a static method called getProps
, this method 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 convert to a Java CompletionStage
using scala.compat.java8.FutureConverts.toJava
, and then map to your own result type.
Below is an example of using our HelloActor
with the ask pattern:
import akka.actor.*;
import play.mvc.*;
import scala.jdk.javaapi.FutureConverters;
import javax.inject.*;
import java.util.concurrent.CompletionStage;
import static akka.pattern.Patterns.ask;
@Singleton
public class Application extends Controller {
final ActorRef helloActor;
@Inject
public Application(ActorSystem system) {
helloActor = system.actorOf(HelloActor.getProps());
}
public CompletionStage<Result> sayHello(String name) {
return FutureConverters.asJava(ask(helloActor, new SayHello(name), 1000))
.thenApply(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 converted to a
CompletionStage
. The resulting promise is aCompletionStage<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.
§Dependency injecting actors
If you prefer, you can have Guice instantiate your actors and bind actor refs to them for your controllers and components to depend on.
For example, if you wanted to have an actor that depended on the Play configuration, you might do this:
import akka.actor.AbstractActor;
import com.typesafe.config.Config;
import javax.inject.Inject;
public class ConfiguredActor extends AbstractActor {
private Config configuration;
@Inject
public ConfiguredActor(Config configuration) {
this.configuration = configuration;
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
ConfiguredActorProtocol.GetConfig.class,
message -> {
sender().tell(configuration.getString("my.config"), self());
})
.build();
}
}
Play provides some helpers to help providing actor bindings. These allow the actor itself to be dependency injected, and allows the actor ref for the actor to be injected into other components. To bind an actor using these helpers, create a module as described in the dependency injection documentation, then mix in the AkkaGuiceSupport
interface and use the bindActor
method to bind the actor:
import com.google.inject.AbstractModule;
import play.libs.akka.AkkaGuiceSupport;
public class MyModule extends AbstractModule implements AkkaGuiceSupport {
@Override
protected void configure() {
bindActor(ConfiguredActor.class, "configured-actor");
}
}
This actor will both be named configured-actor
, and will also be qualified with the configured-actor
name for injection. You can now depend on the actor in your controllers and other components:
import akka.actor.ActorRef;
import play.mvc.*;
import scala.jdk.javaapi.FutureConverters;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.concurrent.CompletionStage;
import static akka.pattern.Patterns.ask;
public class Application extends Controller {
private ActorRef configuredActor;
@Inject
public Application(@Named("configured-actor") ActorRef configuredActor) {
this.configuredActor = configuredActor;
}
public CompletionStage<Result> getConfig() {
return FutureConverters.asJava(
ask(configuredActor, new ConfiguredActorProtocol.GetConfig(), 1000))
.thenApply(response -> ok((String) response));
}
}
§Dependency injecting child actors
The above is good for injecting root actors, but many of the actors you create will be child actors that are not bound to the lifecycle of the Play app, and may have additional state passed to them.
In order to assist in dependency injecting child actors, Play utilises Guice’s AssistedInject support.
Let’s say you have the following actor, which depends on configuration to be injected, plus a key:
import akka.actor.AbstractActor;
import com.google.inject.assistedinject.Assisted;
import com.typesafe.config.Config;
import javax.inject.Inject;
public class ConfiguredChildActor extends AbstractActor {
private final Config configuration;
private final String key;
@Inject
public ConfiguredChildActor(Config configuration, @Assisted String key) {
this.configuration = configuration;
this.key = key;
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(ConfiguredChildActorProtocol.GetConfig.class, this::getConfig)
.build();
}
private void getConfig(ConfiguredChildActorProtocol.GetConfig get) {
sender().tell(configuration.getString(key), self());
}
}
In this case we have used constructor injection - Guice’s assisted inject support is only compatible with constructor injection. Since the key
parameter is going to be provided on creation, not by the container, we have annotated it with @Assisted
.
Now in the protocol for the child, we define a Factory
interface that takes the key
and returns the Actor
:
import akka.actor.Actor;
public class ConfiguredChildActorProtocol {
public static class GetConfig {}
public interface Factory {
public Actor create(String key);
}
}
We won’t implement this, Guice will do that for us, providing an implementation that not only passes our key
parameter, but also locates the Configuration
dependency and injects that. Since the trait just returns an Actor
, when testing this actor we can inject a factor that returns any actor, for example this allows us to inject a mocked child actor, instead of the actual one.
Now, the actor that depends on this can extend InjectedActorSupport
, and it can depend on the factory we created:
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import javax.inject.Inject;
import play.libs.akka.InjectedActorSupport;
public class ParentActor extends AbstractActor implements InjectedActorSupport {
private ConfiguredChildActorProtocol.Factory childFactory;
@Inject
public ParentActor(ConfiguredChildActorProtocol.Factory childFactory) {
this.childFactory = childFactory;
}
@Override
public Receive createReceive() {
return receiveBuilder().match(ParentActorProtocol.GetChild.class, this::getChild).build();
}
private void getChild(ParentActorProtocol.GetChild msg) {
String key = msg.key;
ActorRef child = injectedChild(() -> childFactory.create(key), key);
sender().tell(child, self());
}
}
It uses the injectedChild
to create and get a reference to the child actor, passing in the key. The second parameter (key
in this example) will be used as the child actor’s name.
Finally, we need to bind our actors. In our module, we use the bindActorFactory
method to bind the parent actor, and also bind the child factory to the child implementation:
import com.google.inject.AbstractModule;
import play.libs.akka.AkkaGuiceSupport;
public class MyModule extends AbstractModule implements AkkaGuiceSupport {
@Override
protected void configure() {
bindActor(ParentActor.class, "parent-actor");
bindActorFactory(ConfiguredChildActor.class, ConfiguredChildActorProtocol.Factory.class);
}
}
This will get Guice to automatically bind an instance of ConfiguredChildActorProtocol.Factory
, which will provide an instance of Configuration
to ConfiguredChildActor
when it’s instantiated.
§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.actor.default-dispatcher.fork-join-executor.parallelism-max = 64
akka.actor.debug.receive = on
For Akka logging configuration, see configuring logging.
§Built-in actor system name
By default the name of the Play actor system is application
. You can change this via an entry in the conf/application.conf
:
play.akka.actor-system = "custom-name"
Note: This feature is useful if you want to put your play application
ActorSystem
in an akka cluster.
§Using your own Actor system
While we recommend you use the built in actor system, as it sets up everything such as the correct classloader, lifecycle hooks, etc, there is nothing stopping you from using your own actor system. It is important however to ensure you do the following:
- Register a stop hook to shut the actor system down when Play shuts down
- Pass in the correct classloader from the Play Environment otherwise Akka won’t be able to find your applications classes
- Ensure that you don’t read your akka configuration from the default
akka
config, which is used by Play’s actor system already, as this will cause problems such as when the systems try to bind to the same remote ports
§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:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import play.mvc.*;
public class Application extends Controller {
public CompletionStage<Result> index() {
return CompletableFuture.supplyAsync(this::longComputation)
.thenApply((Integer i) -> ok("Got " + i));
}
}
§Akka Coordinated Shutdown
Play handles the shutdown of the Application
and the Server
using Akka’s Coordinated Shutdown. Find more information in the Coordinated Shutdown common section.
NOTE: Play only handles the shutdown of its internal ActorSystem
. If you are using extra actor systems, make sure they are all terminated and feel free to migrate your termination code to Coordinated Shutdown.
§Akka Cluster
You can make your Play application join an existing Akka Cluster. In that case it is recommended that you leave the cluster gracefully. Play ships with Akka’s Coordinated Shutdown which will take care of that graceful leave.
If you already have custom Cluster Leave code it is recommended that you replace it with Akka’s handling. See Akka docs for more details.
§Updating Akka version
If you want to use a newer version of Akka, one that is not used by Play yet, you can add the following to your build.sbt
file:
// The newer Akka version you want to use.
val akkaVersion = "2.6.21"
// Akka dependencies used by Play
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-actor-typed" % akkaVersion,
"com.typesafe.akka" %% "akka-stream" % akkaVersion,
"com.typesafe.akka" %% "akka-slf4j" % akkaVersion,
"com.typesafe.akka" %% "akka-serialization-jackson" % akkaVersion,
// Only if you are using Akka Testkit
"com.typesafe.akka" %% "akka-testkit" % akkaVersion
)
Of course, other Akka artifacts can be added transitively. Use sbt-dependency-graph to better inspect your build and check which ones you need to add explicitly.
If you haven’t switched to the Netty server backend and therefore are using Play’s default Akka HTTP server backend, you also have to update Akka HTTP. Therefore, you need to add its dependencies explicitly as well:
// The newer Akka HTTP version you want to use.
val akkaHTTPVersion = "10.2.10"
// Akka HTTP dependencies used by Play
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http-core" % akkaHTTPVersion,
// Add this one if you are using HTTP/2
// (e.g. with enabled PlayAkkaHttp2Support sbt plugin in build.sbt)
"com.typesafe.akka" %% "akka-http2-support" % akkaHTTPVersion
)
§Important note on using Akka HTTP 10.5.0 or newer with Scala 3
Starting with Akka version 2.7.0 and Akka HTTP version 10.4.0, those libraries are published under the Business Source License (BSL) v1.1. This means that when using these Akka or Akka HTTP versions (or newer), your company might need to pay license fees. For more details, refer to the Akka License FAQ. On another note, starting with Akka HTTP version 10.5.0 native Scala 3 artifacts get published.
Play does not ship with those newer versions, but instead it defaults to using Akka 2.6 and Akka HTTP 10.2.x. You are free to upgrade to the newer commercial versions, as described in the previous section. However, if you choose to do so and want to use Scala 3, you need to set akkaHttpScala3Artifacts := true
to exclude any Akka HTTP Scala 2.13 artifacts that Play depends on by default:
// ...
// scalaVersion := "3.x.x" // When using Scala 3...
// ...
// ...and if using Akka HTTP version 10.5.x or newer
// you need to set following setting in your build.sbt file:
PlayKeys.akkaHttpScala3Artifacts := true
This is necessary because Play uses the for3Use2_13
cross version workaround to make Akka HTTP 10.2.x work with Scala 3. The above setting disables this behaviour to make sure there are no Akka HTTP Scala 2 artifacts on the classpath (which very likely will conflict with the Akka HTTP Scala 3 artifacts you upgrade to).
Be aware, however, that using this akkaHttpScala3Artifacts
approach only works when the PlayAkkaHttpServer
sbt plugin is enabled. If the plugin is not active, but the play-akka-http-server
dependency is pulled in directly like
lazy val root = project.in(file("."))
// PlayAkkaHttpServer, PlayJava, etc. not activated
.settings(
// ...
Compile / mainClass := Some("play.core.server.ProdServerStart"),
libraryDependencies ++= Seq(
"com.typesafe.play" %% "play-akka-http-server" % "<play_version>",
)
)
you have to manually apply what akkaHttpScala3Artifacts
would do:
val akkaDeps =
Seq("akka-actor", "akka-actor-typed", "akka-slf4j", "akka-serialization-jackson", "akka-stream")
val scala2Deps = Map(
"com.typesafe.akka" -> ("2.6.21", akkaDeps),
"com.typesafe" -> ("0.6.1", Seq("ssl-config-core")),
"com.fasterxml.jackson.module" -> ("2.14.3", Seq("jackson-module-scala"))
)
lazy val root = project.in(file("."))
// PlayAkkaHttpServer, PlayScala, PlayJava, etc. not activated
.settings(
// ...
Compile / mainClass := Some("play.core.server.ProdServerStart"),
libraryDependencies ++= Seq(
"com.typesafe.play" %% "play-akka-http-server" % "<play_version>",
)
// Work around needed because akka-http 10.2.x is not published for Scala 3
excludeDependencies ++=
(if (scalaBinaryVersion.value == "3") {
scala2Deps.flatMap(e => e._2._2.map(_ + "_3").map(ExclusionRule(e._1, _))).toSeq
} else {
Seq.empty
}),
libraryDependencies ++=
(if (scalaBinaryVersion.value == "3") {
scala2Deps.flatMap(e => e._2._2.map(e._1 %% _ % e._2._1).map(_.cross(CrossVersion.for3Use2_13))).toSeq
} else {
Seq.empty
}),
)
§Using Cluster Sharding and Akka HTTP without native Scala 3 artifacts
If you use Scala 3 and Akka HTTP version 10.4 or earlier (which does not provide native Scala 3 artifacts) and want to use Cluster Sharding for Akka Typed you have to apply the for3Use2_13
cross version workaround to make things work:
libraryDependencies += javaClusterSharding
libraryDependencies += ("com.typesafe.akka" %% "akka-cluster-sharding-typed" % "2.6.21").cross(CrossVersion.for3Use2_13)
excludeDependencies += sbt.ExclusionRule("com.typesafe.akka", "akka-cluster-sharding-typed_3")
This configuration ensures that Cluster Sharding for Akka Typed can be used seamlessly with Scala 3 and Akka HTTP versions that lack native Scala 3 artifacts.
Note: When doing such updates, keep in mind that you need to follow Akka’s Binary Compatibility Rules. And if you are manually adding other Akka artifacts, remember to keep the version of all the Akka artifacts consistent since mixed versioning is not allowed.
Note: When resolving dependencies, sbt will get the newest one declared for this project or added transitively. It means that if Play depends on a newer Akka (or Akka HTTP) version than the one you are declaring, Play’s version wins. See more details about how sbt does evictions here.