§Integrating with Akka Typed
Akka 2.6 marked the new typed Actor API (“Akka Typed”) as stable. The typed API is now officially the main API for Akka. In the typed API, each actor needs to declares which message type it is able to handle and the type system enforces that only messages of this type can be sent to the actor. Although Play does not fully adopt Akka Typed, we already provide some APIs to better integrate it in Play applications.
Note: the Akka classic APIs are still fully supported and existing applications can continue to use them. There are no plans to deprecate or remove Akka classic API.
§Akka Actor Typed styles
Akka’s Actor Typed API has two styles:
- a “functional programming” style, based on defining an actor
Behavior
s with values; and - a “object-oriented” style, based on defining an actor
Behavior
s with subclasses.
For instance, here’s an example of a simple actor that says hello back:
- Scala FP
-
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.ActorRef import akka.actor.typed.Behavior object HelloActor { final case class SayHello( name: String, replyTo: ActorRef[String], ) def create(): Behavior[SayHello] = { Behaviors.receiveMessage[SayHello] { case SayHello(name, replyTo) => replyTo ! s"Hello, $name" Behaviors.same } } }
- Scala OO
-
import akka.actor.typed.scaladsl.AbstractBehavior import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.ActorRef import akka.actor.typed.Behavior object HelloActor { final case class SayHello( name: String, replyTo: ActorRef[String], ) def create(): Behavior[HelloActor.SayHello] = { Behaviors.setup(context => new HelloActor(context)) } } final class HelloActor private ( context: ActorContext[HelloActor.SayHello], ) extends AbstractBehavior(context) { import HelloActor._ def onMessage(msg: SayHello): HelloActor = { msg.replyTo ! s"Hello, ${msg.name}" this } }
- Java FP
-
import akka.actor.typed.ActorRef; import akka.actor.typed.Behavior; import akka.actor.typed.javadsl.Behaviors; public final class HelloActor { public static final class SayHello { public final String name; public final ActorRef<String> replyTo; public SayHello(String name, ActorRef<String> replyTo) { this.name = name; this.replyTo = replyTo; } } public static Behavior<HelloActor.SayHello> create() { return Behaviors.receiveMessage( (SayHello message) -> { message.replyTo.tell("Hello, " + message.name); return Behaviors.same(); }); } }
- Java OO
-
import akka.actor.typed.ActorRef; import akka.actor.typed.Behavior; import akka.actor.typed.javadsl.AbstractBehavior; import akka.actor.typed.javadsl.ActorContext; import akka.actor.typed.javadsl.Behaviors; import akka.actor.typed.javadsl.Receive; public final class HelloActor extends AbstractBehavior<HelloActor.SayHello> { public static final class SayHello { public final String name; public final ActorRef<String> replyTo; public SayHello(String name, ActorRef<String> replyTo) { this.name = name; this.replyTo = replyTo; } } public static Behavior<HelloActor.SayHello> create() { return Behaviors.setup((ctx) -> new HelloActor(ctx)); } private HelloActor(ActorContext<HelloActor.SayHello> context) { super(context); } @Override public Receive<SayHello> createReceive() { return newReceiveBuilder().onMessage(SayHello.class, this::onHello).build(); } private Behavior<SayHello> onHello(SayHello message) { message.replyTo.tell("Hello, " + message.name); return this; } }
While here is an example of an actor that depends on Play’s Configuration
in order to return configuration values:
- Scala FP
-
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import com.google.inject.Provides import play.api.libs.concurrent.ActorModule import play.api.Configuration object ConfiguredActor extends ActorModule { type Message = GetConfig final case class GetConfig(replyTo: ActorRef[String]) @Provides def create(configuration: Configuration): Behavior[GetConfig] = { Behaviors.setup { _ => val config = configuration.get[String]("my.config") Behaviors.receiveMessage[GetConfig] { case GetConfig(replyTo) => replyTo ! config Behaviors.same } } } }
- Scala OO
-
import javax.inject.Inject import akka.actor.typed.scaladsl.AbstractBehavior import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import play.api.Configuration object ConfiguredActor { final case class GetConfig(replyTo: ActorRef[String]) def create( configuration: Configuration, ): Behavior[ConfiguredActor.GetConfig] = { Behaviors.setup { context => new ConfiguredActor(context, configuration) } } } final class ConfiguredActor private ( context: ActorContext[ConfiguredActor.GetConfig], configuration: Configuration, ) extends AbstractBehavior(context) { import ConfiguredActor._ val config = configuration.get[String]("my.config") def onMessage(msg: GetConfig): ConfiguredActor = { msg.replyTo ! config this } }
- Java FP
-
import akka.actor.typed.ActorRef; import akka.actor.typed.Behavior; import akka.actor.typed.javadsl.Behaviors; import com.typesafe.config.Config; public final class ConfiguredActor { public static final class GetConfig { public final ActorRef<String> replyTo; public GetConfig(ActorRef<String> replyTo) { this.replyTo = replyTo; } } public static Behavior<ConfiguredActor.GetConfig> create(Config config) { String myConfig = config.getString("my.config"); return Behaviors.receiveMessage( (GetConfig message) -> { message.replyTo.tell(myConfig); return Behaviors.same(); }); } }
- Java OO
-
import akka.actor.typed.ActorRef; import akka.actor.typed.Behavior; import akka.actor.typed.javadsl.AbstractBehavior; import akka.actor.typed.javadsl.ActorContext; import akka.actor.typed.javadsl.Behaviors; import akka.actor.typed.javadsl.Receive; import com.typesafe.config.Config; public final class ConfiguredActor extends AbstractBehavior<ConfiguredActor.GetConfig> { public static final class GetConfig { public final ActorRef<String> replyTo; public GetConfig(ActorRef<String> replyTo) { this.replyTo = replyTo; } } private final String config; public static Behavior<ConfiguredActor.GetConfig> create(Config config) { return Behaviors.setup((ctx) -> new ConfiguredActor(ctx, config)); } private ConfiguredActor(ActorContext<ConfiguredActor.GetConfig> context, Config config) { super(context); this.config = config.getString("my.config"); } @Override public Receive<GetConfig> createReceive() { return newReceiveBuilder().onMessage(GetConfig.class, this::onGetConfig).build(); } private Behavior<GetConfig> onGetConfig(GetConfig message) { message.replyTo.tell(config); return this; } }
§Dependency Injection
If your actor’s behavior has mutable state, as is sometimes common in the object-oriented style, make sure you don’t share the same Behavior
instance for multiple ActorRef
s. Here are some general ways to avoid the problem:
- Consider a design without mutable state;
- Don’t leak the
Behavior
instance by only exposing theActorRef
instance, for example by only binding theActorRef
; - If the objective is to only have one single instance of the actor, then make sure that both the
Behavior
andActorRef
are singletons, for example by using@Singleton
or.asEagerSingleton
; - If, instead, there are meant to be multiple instances of the same actor then make sure both
Behavior
andActorRef
are named singletons, in Guice by using@Named
or.annotatedWith(Names.named(..))
.
§Compile-time dependency injection
Using compile-time dependency injection for Akka Actor Typed requires creating the actor Behavior
value and using it to spawn the actor:
- Scala
-
import akka.actor.typed.scaladsl.adapter._ import play.api._ import play.api.routing.Router final class AppComponents(context: ApplicationLoader.Context) extends BuiltInComponentsFromContext(context) with NoHttpFiltersComponents { val router = Router.empty val helloActor = { actorSystem.spawn(HelloActor.create(), "hello-actor") } val configuredActor = { val behavior = ConfiguredActor.create(configuration) actorSystem.spawn(behavior, "configured-actor") } val main = new Main(helloActor, configuredActor) }
- Java
-
import akka.actor.typed.ActorRef; import akka.actor.typed.javadsl.Adapter; import java.util.Collections; import java.util.List; import play.ApplicationLoader; import play.BuiltInComponentsFromContext; import play.mvc.EssentialFilter; import play.routing.Router; public final class AppComponents extends BuiltInComponentsFromContext { public final ActorRef<HelloActor.SayHello> helloActor; public final ActorRef<ConfiguredActor.GetConfig> configuredActor; public final Main main; public AppComponents(ApplicationLoader.Context context) { super(context); helloActor = Adapter.spawn(actorSystem(), HelloActor.create(), "hello-actor"); configuredActor = Adapter.spawn(actorSystem(), ConfiguredActor.create(config()), "configured-actor"); main = new Main(helloActor, configuredActor); } @Override public Router router() { return Router.empty(); } @Override public List<EssentialFilter> httpFilters() { return Collections.emptyList(); } }
§Runtime dependency injection
For runtime dependency injection use the “typed” methods in AkkaGuiceSupport
, if using the functional-programming style. For the object-oriented style you must write a Provider
for your ActorRef
and bind it.
For instance, given a component in your application or system that needs injecting, like this one:
- Scala
-
import javax.inject.Inject import javax.inject.Singleton import akka.actor.typed.ActorRef @Singleton final class Main @Inject() ( val helloActor: ActorRef[HelloActor.SayHello], val configuredActor: ActorRef[ConfiguredActor.GetConfig], )
- Java
-
import akka.actor.typed.ActorRef; import javax.inject.Inject; import javax.inject.Singleton; @Singleton public final class Main { public final ActorRef<HelloActor.SayHello> helloActor; public final ActorRef<ConfiguredActor.GetConfig> configuredActor; @Inject public Main( ActorRef<HelloActor.SayHello> helloActor, ActorRef<ConfiguredActor.GetConfig> configuredActor) { this.helloActor = helloActor; this.configuredActor = configuredActor; } }
You can define a Guice Module
like so:
- Scala FP
-
import com.google.inject.AbstractModule import play.api.libs.concurrent.AkkaGuiceSupport object AppModule extends AbstractModule with AkkaGuiceSupport { override def configure() = { bindTypedActor(HelloActor.create(), "hello-actor") // uses "create" method bindTypedActor(ConfiguredActor, "configured-actor") // uses the object itself } }
- Scala OO
-
import javax.inject.Inject import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.ActorRef import akka.actor.ActorSystem import com.google.inject.AbstractModule import com.google.inject.Provider import com.google.inject.TypeLiteral import play.api.libs.concurrent.AkkaGuiceSupport import play.api.Configuration object AppModule extends AbstractModule with AkkaGuiceSupport { override def configure() = { bindTypedActor(HelloActor.create(), "hello-actor") bind(new TypeLiteral[ActorRef[ConfiguredActor.GetConfig]]() {}) .toProvider(classOf[ConfiguredActorProvider]) .asEagerSingleton() } private class ConfiguredActorProvider @Inject() ( actorSystem: ActorSystem, configuration: Configuration, ) extends Provider[ActorRef[ConfiguredActor.GetConfig]] { def get() = { val behavior = ConfiguredActor.create(configuration) actorSystem.spawn(behavior, "configured-actor") } } }
- Java FP
-
import akka.actor.ActorSystem; import akka.actor.typed.ActorRef; import akka.actor.typed.javadsl.Adapter; import com.google.inject.AbstractModule; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.typesafe.config.Config; import javax.inject.Inject; public class AppModule extends AbstractModule { @Override protected void configure() { bind(new TypeLiteral<ActorRef<HelloActor.SayHello>>() {}) .toProvider(HelloActorProvider.class) .asEagerSingleton(); bind(new TypeLiteral<ActorRef<ConfiguredActor.GetConfig>>() {}) .toProvider(ConfiguredActorProvider.class) .asEagerSingleton(); } public static class HelloActorProvider implements Provider<ActorRef<HelloActor.SayHello>> { private final ActorSystem actorSystem; @Inject public HelloActorProvider(ActorSystem actorSystem) { this.actorSystem = actorSystem; } @Override public ActorRef<HelloActor.SayHello> get() { return Adapter.spawn(actorSystem, HelloActor.create(), "hello-actor"); } } public static class ConfiguredActorProvider implements Provider<ActorRef<ConfiguredActor.GetConfig>> { private final ActorSystem actorSystem; private final Config config; @Inject public ConfiguredActorProvider(ActorSystem actorSystem, Config config) { this.actorSystem = actorSystem; this.config = config; } @Override public ActorRef<ConfiguredActor.GetConfig> get() { return Adapter.spawn(actorSystem, ConfiguredActor.create(config), "configured-actor"); } } }
- Java OO
-
import akka.actor.ActorSystem; import akka.actor.typed.ActorRef; import akka.actor.typed.javadsl.Adapter; import com.google.inject.AbstractModule; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.typesafe.config.Config; import javax.inject.Inject; public class AppModule extends AbstractModule { @Override protected void configure() { bind(new TypeLiteral<ActorRef<HelloActor.SayHello>>() {}) .toProvider(HelloActorProvider.class) .asEagerSingleton(); bind(new TypeLiteral<ActorRef<ConfiguredActor.GetConfig>>() {}) .toProvider(ConfiguredActorProvider.class) .asEagerSingleton(); } public static class HelloActorProvider implements Provider<ActorRef<HelloActor.SayHello>> { private final ActorSystem actorSystem; @Inject public HelloActorProvider(ActorSystem actorSystem) { this.actorSystem = actorSystem; } @Override public ActorRef<HelloActor.SayHello> get() { return Adapter.spawn(actorSystem, HelloActor.create(), "hello-actor"); } } public static class ConfiguredActorProvider implements Provider<ActorRef<ConfiguredActor.GetConfig>> { private final ActorSystem actorSystem; private final Config config; @Inject public ConfiguredActorProvider(ActorSystem actorSystem, Config config) { this.actorSystem = actorSystem; this.config = config; } @Override public ActorRef<ConfiguredActor.GetConfig> get() { return Adapter.spawn(actorSystem, ConfiguredActor.create(config), "configured-actor"); } } }
§Using the AskPattern
& Typed Scheduler
When interacting with actors from outside of another Actor, for example from a Controller
, you need to use AskPattern.ask
to send a message to the actor and get a response. The AskPattern.ask
method requires a akka.actor.typed.Scheduler
that you can obtain via Dependency Injection.
§Runtime dependency injection
Runtime dependency injection works as any other runtime DI module in Play. The Scheduler
is part of the default bindings, so the module is enabled automatically, and an instance is available for injection.
§Compile-time dependency injection
If you’re using compile-time DI, you can get have access to the Scheduler
by using the components like below:
- Java
-
import play.ApplicationLoader; import play.BuiltInComponentsFromContext; import play.components.AkkaTypedComponents; import play.controllers.AssetsComponents; import play.filters.components.HttpFiltersComponents; import play.routing.Router; public class ComponentsWithTypedScheduler extends BuiltInComponentsFromContext implements AkkaTypedComponents, AssetsComponents, HttpFiltersComponents { public ComponentsWithTypedScheduler(ApplicationLoader.Context context) { super(context); } @Override public Router router() { return Router.empty(); } }
- Scala
-
import play.api.libs.concurrent.AkkaTypedComponents import play.api.routing.Router import play.api.Application import play.api.ApplicationLoader import play.api.ApplicationLoader.Context import play.api.BuiltInComponentsFromContext import play.filters.HttpFiltersComponents class MyApplicationLoaderUsingTypedScheduler extends ApplicationLoader { override def load(context: Context): Application = { new ComponentsWithTypedScheduler(context).application } } class ComponentsWithTypedScheduler(context: Context) extends BuiltInComponentsFromContext(context) with HttpFiltersComponents with AkkaTypedComponents { override lazy val router: Router = Router.empty }