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!!
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:
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.