Coexistence
Dependency
To use Akka Actor Typed, you must add the following dependency in your project:
- sbt
libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % "2.5.32"
- Maven
<dependencies> <dependency> <groupId>com.typesafe.akka</groupId> <artifactId>akka-actor-typed_2.12</artifactId> <version>2.5.32</version> </dependency> </dependencies>
- Gradle
dependencies { implementation "com.typesafe.akka:akka-actor-typed_2.12:2.5.32" }
Introduction
We believe Akka Typed will be adopted in existing systems gradually and therefore it’s important to be able to use typed and untyped actors together, within the same ActorSystem
. Also, we will not be able to integrate with all existing modules in one big bang release and that is another reason for why these two ways of writing actors must be able to coexist.
There are two different ActorSystem
s: akka.actor.ActorSystem
and akka.actor.typed.ActorSystem
.
Currently the typed actor system is implemented using an untyped actor system under the hood. This may change in the future.
Typed and untyped can interact the following ways:
- untyped actor systems can create typed actors
- typed actors can send messages to untyped actors, and opposite
- spawn and supervise typed child from untyped parent, and opposite
- watch typed from untyped, and opposite
- untyped actor system can be converted to a typed actor system
In the examples the akka.actor
package is aliased to untyped
.
- Scala
-
source
import akka.{ actor => untyped }
The examples use fully qualified class names for the untyped classes to distinguish between typed and untyped classes with the same name.
Untyped to typed
While coexisting your application will likely still have an untyped ActorSystem. This can be converted to a typed ActorSystem so that new code and migrated parts don’t rely on the untyped system:
- Scala
-
source
// adds support for typed actors to an untyped actor system and context import akka.actor.typed.scaladsl.adapter._ val system = akka.actor.ActorSystem("UntypedToTypedSystem") val typedSystem: ActorSystem[Nothing] = system.toTyped
- Java
-
source
// In java use the static methods on Adapter to convert from typed to untyped import akka.actor.typed.javadsl.Adapter; akka.actor.ActorSystem untypedActorSystem = akka.actor.ActorSystem.create(); ActorSystem<Void> typedActorSystem = Adapter.toTyped(untypedActorSystem);
Then for new typed actors here’s how you create, watch and send messages to it from an untyped actor.
- Scala
-
source
object Typed { sealed trait Command final case class Ping(replyTo: ActorRef[Pong.type]) extends Command case object Pong val behavior: Behavior[Command] = Behaviors.receive { (context, message) => message match { case Ping(replyTo) => context.log.info(s"${context.self} got Ping from $replyTo") // replyTo is an untyped actor that has been converted for coexistence replyTo ! Pong Behaviors.same } } }
- Java
-
source
public abstract static class Typed { interface Command {} public static class Ping implements Command { public final akka.actor.typed.ActorRef<Pong> replyTo; public Ping(ActorRef<Pong> replyTo) { this.replyTo = replyTo; } } public static class Pong {} public static Behavior<Command> behavior() { return Behaviors.receive(Typed.Command.class) .onMessage( Typed.Ping.class, (context, message) -> { message.replyTo.tell(new Pong()); return same(); }) .build(); } }
The top level untyped actor is created in the usual way:
- Scala
-
source
val untypedActor = system.actorOf(Untyped.props())
- Java
-
source
akka.actor.ActorSystem as = akka.actor.ActorSystem.create(); akka.actor.ActorRef untyped = as.actorOf(Untyped.props());
Then it can create a typed actor, watch it, and send a message to it:
- Scala
-
source
class Untyped extends untyped.Actor with ActorLogging { // context.spawn is an implicit extension method val second: ActorRef[Typed.Command] = context.spawn(Typed.behavior, "second") // context.watch is an implicit extension method context.watch(second) // self can be used as the `replyTo` parameter here because // there is an implicit conversion from akka.actor.ActorRef to // akka.actor.typed.ActorRef second ! Typed.Ping(self) override def receive = { case Typed.Pong => log.info(s"$self got Pong from ${sender()}") // context.stop is an implicit extension method context.stop(second) case untyped.Terminated(ref) => log.info(s"$self observed termination of $ref") context.stop(self) } }
- Java
-
source
public static class Untyped extends AbstractActor { public static akka.actor.Props props() { return akka.actor.Props.create(Untyped.class); } private final akka.actor.typed.ActorRef<Typed.Command> second = Adapter.spawn(getContext(), Typed.behavior(), "second"); @Override public void preStart() { Adapter.watch(getContext(), second); second.tell(new Typed.Ping(Adapter.toTyped(getSelf()))); } @Override public Receive createReceive() { return receiveBuilder() .match( Typed.Pong.class, message -> { Adapter.stop(getContext(), second); }) .match( akka.actor.Terminated.class, t -> { getContext().stop(getSelf()); }) .build(); } }
There is one import
that is needed to make that work. We import the Adapter class and call static methods for conversion.
- Scala
-
source
// adds support for typed actors to an untyped actor system and context import akka.actor.typed.scaladsl.adapter._
- Java
-
source
// In java use the static methods on Adapter to convert from typed to untyped import akka.actor.typed.javadsl.Adapter;
That adds some implicit extension methods that are added to untyped and typed ActorSystem
and ActorContext
in both directions. To convert between typed and untyped there are adapter methods in akka.actor.typed.javadsl.Adapter
. Note the inline comments in the example above.
Typed to untyped
Let’s turn the example upside down and first start the typed actor and then the untyped as a child.
The following will show how to create, watch and send messages back and forth from a typed actor to this untyped actor:
- Scala
-
source
object Untyped { def props(): untyped.Props = untyped.Props(new Untyped) } class Untyped extends untyped.Actor { override def receive = { case Typed.Ping(replyTo) => replyTo ! Typed.Pong } }
- Java
-
source
public static class Untyped extends AbstractActor { public static akka.actor.Props props() { return akka.actor.Props.create(Untyped.class); } @Override public Receive createReceive() { return receiveBuilder() .match( Typed.Ping.class, message -> { message.replyTo.tell(new Typed.Pong()); }) .build(); } }
Creating the actor system and the typed actor:
- Scala
-
source
val system = untyped.ActorSystem("TypedWatchingUntyped") val typed = system.spawn(Typed.behavior, "Typed")
- Java
-
source
ActorSystem as = ActorSystem.create(); ActorRef<Typed.Command> typed = Adapter.spawn(as, Typed.behavior(), "Typed");
Then the typed actor creates the untyped actor, watches it and sends and receives a response:
- Scala
-
source
object Typed { final case class Ping(replyTo: akka.actor.typed.ActorRef[Pong.type]) sealed trait Command case object Pong extends Command val behavior: Behavior[Command] = Behaviors.setup { context => // context.actorOf is an implicit extension method val untyped = context.actorOf(Untyped.props(), "second") // context.watch is an implicit extension method context.watch(untyped) // illustrating how to pass sender, toClassic is an implicit extension method untyped.tell(Typed.Ping(context.self), context.self.toClassic) Behaviors .receivePartial[Command] { case (context, Pong) => // it's not possible to get the sender, that must be sent in message // context.stop is an implicit extension method context.stop(untyped) Behaviors.same } .receiveSignal { case (_, akka.actor.typed.Terminated(_)) => Behaviors.stopped } } }
- Java
-
source
public abstract static class Typed { public static class Ping { public final akka.actor.typed.ActorRef<Pong> replyTo; public Ping(ActorRef<Pong> replyTo) { this.replyTo = replyTo; } } interface Command {} public static class Pong implements Command {} public static Behavior<Command> behavior() { return akka.actor.typed.javadsl.Behaviors.setup( context -> { akka.actor.ActorRef second = Adapter.actorOf(context, Untyped.props(), "second"); Adapter.watch(context, second); second.tell( new Typed.Ping(context.getSelf().narrow()), Adapter.toClassic(context.getSelf())); return akka.actor.typed.javadsl.Behaviors.receive(Typed.Command.class) .onMessage( Typed.Pong.class, (_ctx, message) -> { Adapter.stop(context, second); return same(); }) .onSignal(akka.actor.typed.Terminated.class, (_ctx, sig) -> stopped()) .build(); }); } }
Supervision
The default supervision for untyped actors is to restart whereas for typed it is to stop. When combining untyped and typed actors the default supervision is based on the default behavior of the child i.e. if an untyped actor creates a typed child, its default supervision will be to stop. If a typed actor creates an untyped child, its default supervision will be to restart.