Search code examples
javakotlinakkaactorakka-typed

How to use AskPattern.ask in Akka Typed? (Java/Kotlin)


I'm having a bit of trouble using the Ask Pattern in Akka Typed. I'm trying to send a message from outside an actor, and attempted to follow this example. However, I think that my setup is a bit different from the example:

object SimulatorManager {

    fun create(): Behavior<SimulatorManagerMessage> {
        val state = ... // initialize state
        return waitingToStartSimulation(state)
    }

    private fun waitingToStartSimulation(state: SimulatorManagerState): Behavior<SimulatorManagerMessage> {
        return Behaviors.receive { context, message ->
            when (message) {
                is StartSimulation -> {
                    // start simulation and update state
                    waitingToStartSimulation(state)
                }
                else -> Behaviors.same()
            }
        }
    }
}

sealed class SimulatorManagerMessage

data class StartSimulation(
    val consumer: ConsumerCredentials,
    val storeId: Long,
    val itemId: Long,
    val simTimeIncrement: Double,
    val realTimeIncrement: Double,
) : SimulatorManagerMessage()

I'm trying to send a StartSimulation message to SimulatorManager from outside of the Actor System. However, I'm getting stuck on what to input for the replyTo function parameter.

class SimulatorController(
    val systemRef: ActorSystem<SimulatorManagerMessage>,
    private val simManagerRef: ActorRef<SimulatorManagerMessage>
) {

    fun startSimulation(request: StartSimulationRequest) {
        val msg = request.toMessage()
        val result = AskPattern.ask<SimulatorManagerMessage, SimulatorController>(
            simManagerRef,
            { replyTo ->  },        // what should go here? 
            Duration.ofSeconds(15),
            systemRef.scheduler()
        )
    }
}

It says that the type of the parameter should be Function<ActorRef<SimulatorController!>!, SimulatorManagerMessage!>!, but I don't know how to go about creating a function like this. Any help would be appreciated!!


Solution

  • The replyTo -> function constructs the message to send and injects a reference to the actor which will receive the message in reply.

    This is because, in order to be asynchronous, what the ask pattern effectively does is:

    • send a message to some actor (call it "A")
    • spawn an actor (actor "B") for the sole purpose of receiving the reply and completing a future with that reply (or failing that future if the timeout is reached; actor "B" will stop itself as soon as it receives a message or time out)

    In classic (untyped) Akka, the message would be constructed the recipient can determine the sender of every message, so the ask pattern is pretty simple (though, when sending an ask from an actor, you have to be careful with the resulting future: basically the only safe thing you can do is adapt the message and pipe it to your mailbox).

    In Akka Typed, because the ActorRef now governs which messages can be sent, sender isn't available. So now you have to explicitly model the type of the expected reply in the messages making up the protocol.

    This is where I confess that this is the first Kotlin I've written, but you might have something like:

    sealed class GradebookCommand
    
    data class GetGradeFor(val student: String, val replyTo: ActorRef<GradeForStudent>): GradebookCommand()
    
    data class GradeForStudent(val student: String, val score: Int, val outOf: Int)
    
    sealed class GradebookRouterCommand
    
    data class GetGradebookActorFor(val classId: String, val replyTo: ActorRef<GradebookIs>): GradebookRouterCommand()
    
    data class GradebookIs(val classId: String, val gradebookActor: ActorRef<GradebookCommand>)
    

    So assuming that gradebookRouter is an ActorRef<GradebookRouterCommand>, you'd ask with

    val classId: String = "asdf"
    val gradebookFuture: CompletionStage<GradebookIs> =
      AskPattern.ask<GradebookRouterCommand, GradebookIs>(
        gradebookRouter,
        { replyTo -> GetGradebookActorFor(classId, replyTo) },
        Duration.ofSeconds(15),
        systemRef.scheduler
      )
    

    gradebookFuture will eventually be completed with the GradebookIs message that gradebookRouter caused to be sent in reply (I use that wording because, one of the benefits of being explicit about replyTo is that it facilitates delegating the work to another actor: you can do that in Akka Classic also, but there's some subtlety around preserving the sender which is easy to mess up).

    // This is perhaps where my Kotlin/Java futures code goes off the rails
    val student: String = "Parry Hotter"
    val gradeFuture: CompletionStage<GradeForStudent> =
      gradebookFuture.thenComposeAsync({ gradebook ->
        AskPattern.ask<GradebookCommand, GradeForStudent>(
          gradebook,
          { replyTo -> GetGradeFor(student, replyTo) },
          Duration.ofSeconds(15),
          systemRef.scheduler
        )})
    

    Disclaimer: the subtleties of Java/Kotlin's type system relative to Scala's (e.g around covariance/contravariance) may make that code not work, but I hope that it makes things reasonably clear.

    TL;DR: make sure to have fields in your messages that serve as Reply-To addresses. They don't have to be named replyTo, though that's definitely emerged as a convention.