scalaakkaakka-typed

How can I get the ActorRef from the Receptionist so my actor can send messages to that actor


So I have 2 actors, ClientIn and Maker.

I need to have a reference of Maker inside of ClientIn, so I can forward some messages that ClientIn recieves to Maker.

I added Maker to the receptionist, but since clientIn is created first, and maker 2nd I'm not sure how to query to fetch the actorRef.

Actually I'm just confused how I can now query the receptionist so that clientIn will get the reference to the Maker actor.

Here is what I have:

// guardian actor constructor:

Behaviors.setup[Any] { context =>
    implicit val ec: ExecutionContext = context.executionContext

    val system = context.system

    val clientIn = context.spawn(ClientIn(), "ClientIn")

    val maker = context.spawn(Maker(clientOut), "mmaker")

    ....

}    

ClientIn.scala:

object ClientIn {
  sealed trait ClientInCommand
  case class Watch(symbol: String) extends ClientInCommand
  case class Command(value: String) extends ClientInCommand
  private case class ListingResponse(listing: Receptionist.Listing)
      extends ClientInCommand

  def apply(): Behavior[ClientInCommand] = {

    Behaviors.setup { context =>
      context.log.info(s"ClientIn starting...${context.self}")

      val listingResponseAdapter =
        context.messageAdapter[Receptionist.Listing](ListingResponse.apply)

      var maker: Option[ActorRef[Maker.MMCommand]] = None

      Behaviors.receiveMessage[ClientInCommand] {
        case Watch(symbol) =>
          context.log.info(s"ClientIn watch message received..$symbol")
          Behaviors.same
        case Command(value) =>
          context.log.info(s"ClientInCommand.Command with value:$value")

          // TODO: Maker ! RecieveCommand(value)
          Behaviors.same
        case ListingResponse(Maker.makerKey.Listing(listings)) =>
          maker = Some(listings.head)
          Behaviors.same

      }
    }

  }
}      

Maker.scala:

object Maker {
  val makerKey = ServiceKey[MMCommand]("makerKey")

  sealed trait MMCommand
  case object Startup extends MMCommand
  case object Shutdown extends MMCommand

  def apply(): Behavior[MMCommand] = {

    Behaviors.setup { context =>

      context.system.receptionist ! Receptionist.Register(
        makerKey,
        context.self
      )

      Behaviors.receiveMessage[MMCommand] {
        case Startup =>
          context.log.info("Maker startup message received..")
          Behaviors.same
        case Shutdown =>
          context.log.info("Maker shutdown message received..")
          Behaviors.same
      }

    }

  }
}

Solution

  • You can send a Receptionist.Subscribe(Maker.makerKey) message to the receptionist, as described in the Akka documentation on Actor discovery:

    context.system.receptionist ! Receptionist.Subscribe(Maker.makerKey, listingResponseAdapter)
    

    Quoting the documentation:

    It will send Listing messages to the subscriber, first with the set of entries upon subscription, then whenever the entries for a key are changed.

    This allows it to receive the maker ActorRef even if it is registered with the receptionist after the ClientIn actor is spawned. However, it is possible in principle that ClientIn may receive other messages such as Command before this happens. It will need to handle these cases, perhaps by stashing or dropping commands until the Maker has been registered.

    In this specific example, it would be simpler to avoid this requirement by spawning the Maker first, and passing its ActorRef to ClientIn.apply:

    val maker = context.spawn(Maker(), "mmaker")
    val clientIn = context.spawn(ClientIn(maker), "ClientIn")
    

    Then, in ClientIn:

    def apply(maker: ActorRef[Maker.MMCommand]): Behavior[ClientInCommand] = {
      Behaviors.setup { context =>
        context.log.info(s"ClientIn starting...${context.self}")
        Behaviors.receiveMessage[ClientInCommand] {
          case Watch(symbol) =>
            context.log.info(s"ClientIn watch message received..$symbol")
            Behaviors.same
          case Command(value) =>
            context.log.info(s"ClientInCommand.Command with value:$value")
            maker ! Maker.ReceiveCommand(value)
            Behaviors.same
        }
      }
    }
    

    Note that the ActorRef returned from spawn is able to receive messages immediately, even if the actor hasn't fully initialized.