Send message to parent actor in Akka Typed

1.5k views Asked by At

Title is self-explanatory, I want to be able to send a message to a parent actor (meaning I want parent's ActorRef). In Akka Classic (untyped), the ActorRef for a parent actor can be obtained from the child's ActorContext via:

context.parent

(see, for instance, this question (in Java)).

However, the akka.actor.typed.scaladsl.ActorContext in Akka Typed does not expose an ActorRef for the parent. Is there an idiomatic means in Scala to obtain an ActorRef for the parent actor?

2

There are 2 answers

0
Levi Ramsey On BEST ANSWER

If you're in typed Akka, the only [Scala] type that could encompass ActorRefs of all possible parent actors is ActorRef[Nothing], which is an ActorRef you can't send messages to, so that's of limited utility.

At least for as long as the classic APIs exist:

import akka.actor.typed.scaladsl.adapter._

type ClassicActorRef = akka.actor.ActorRef

val parentActorRef = context.toClassic.parent

This will be an untyped ActorRef, i.e. you're free to send messages which the parent actor will never accept.

If you want a typed reference to an actor's parent, you'll need to embed that when spawning the child actor, just as if you want a typed reference to the sender of the current message you need to embed replyTos in your protocol.

(context.sender is absent in the typed ActorContext for the same reason that context.parent is absent; the workaround for replicating classic context.sender is analogous: context.toClassic.sender)

0
rfreytag On

TLDR: Inject the parent actor reference into the child when creating it.

Akka Typed enforces strict protocols, so you need to make it absolutely clear that "this actor talks to another actor". The accepted answer is a workaround (casting to classic and using the parent), but has its downsides: now you do not enforce types anymore.

Here is some code that should get you started. See how all the types are enforced. You can model the traits differently, but you should get the drift:

object ParentActor {
  sealed trait Command 
  
  case class DoSomething() extends Command
  
  // you do not have to do this, but creating another trait
  // allows you to narrow the amount of messages the parent can receive from the child
  sealed trait ChildNotification extends Command
  case class MessageFromChild() extends ChildNotification

  
  def apply(): Behavior[Command] = {
    Behaviors.receive( (context, message) => 
      message match {
        case DoSomething() =>
          // create a child that knows about its parent
          context.spawn(ChildActor(context.self), "child")
          Behaviors.same

        case MessageFromChild() =>
          context.log.info("I received a message from my child")
          Behaviors.same
      })
  }
}

object ChildActor {
  sealed trait Command
  case class Work() extends Command
  
  // inject the parent here (or any other actor that matches the signature)
  def apply(parent: ActorRef[ParentActor.ChildNotification]): Behavior[Command] = {
     Behaviors.receive( (context, message) => 
       message match {
         case Work() =>
           // send message to parent actor (or any other actor with that type)
           parent ! ParentActor.MessageFromChild()
           Behaviors.same

     })
  }
}

By the way, I am using the "functional" syntax of akka typed, but you can use the more "object-oriented" syntax as well. It follows the same approach.