Coexistence
Dependency
To use Akka Actor Typed, you must add the following dependency in your project:
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
.
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
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
The top level untyped actor is created in the usual way:
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
There is one import
that is needed to make that work.
- Scala
-
source
// adds support for typed actors to an untyped actor system and context import akka.actor.typed.scaladsl.adapter._
- Java
That adds some implicit extension methods that are added to untyped and typed ActorSystem
and ActorContext
in both directions. 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
Creating the actor system and the typed actor:
- Scala
-
source
val system = untyped.ActorSystem("TypedWatchingUntyped") val typed = system.spawn(Typed.behavior, "Typed")
- Java
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
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.