Reputation: 991
I am new to Akka, and I am converting traditional services to actors. I am not having too much trouble understanding most of it, but I am a little bit confused about the right way to ask two other actors for information.
I tried the following code, but it seems pretty heavy for requesting two pieces of information, so I am wondering if there are ways to improve on what I am trying to do.
public class ActorA extends AbstractBehavior<ActorA.Command> {
public interface Command { }
public record ExampleCommand() implements ActorA.Command { }
private ActorA(ActorContext<ActorA.Command> context) {
super(context);
}
public static Behavior<ActorA.Command> create() {
return Behaviors.setup(ActorA::new);
}
@Override
public Receive<ActorA.Command> createReceive() {
return newReceiveBuilder()
.onMessage(ActorA.ExampleCommand.class, this::onProcessVote)
.build();
}
private Behavior<ActorA.Command> onProcessVote(ActorA.ExampleCommand command) {
ActorContext<ActorA.Command> actorCtx = getContext();
CompletableFuture<ActorRef<ActorB.Command>> actorBRefFuture =
ActorUtil.findActor(actorCtx.getSystem(), ActorB.serviceKey);
CompletableFuture<ActorRef<ActorC.Command>> actorCRefFuture =
ActorUtil.findActor(actorCtx.getSystem(), ActorC.serviceKey);
ActorRef<String> actorBPropReplyAdapter = actorCtx.messageAdapter(String.class, WrappedResponse::new);
CompletableFuture<String> actorBPropHolder = new CompletableFuture<>();
actorBRefFuture.thenAccept(actorRef -> actorCtx.ask(
String.class, actorRef, Duration.ofSeconds(1),
(ActorRef<String> ref) -> new ActorB.GetStringProp(actorBPropReplyAdapter),
(stringProp, error) -> {
if (stringProp != null) {
actorBPropHolder.complete(stringProp);
}
return null;
}));
ActorRef<String> actorCPropReplyAdapter = actorCtx.messageAdapter(String.class, WrappedResponse::new);
CompletableFuture<String> actorCPropHolder = new CompletableFuture<>();
actorCRefFuture.thenAccept(actorRef -> actorCtx.ask(
String.class, actorRef, Duration.ofSeconds(1),
(ActorRef<String> ref) -> new ActorC.GetStringProp(actorCPropReplyAdapter),
(stringProp, error) -> {
if (stringProp != null) {
actorCPropHolder.complete(stringProp);
}
return null;
}));
actorBPropHolder.thenCombine(actorCPropHolder, (actorBProp, actorCProp) -> {
// Do something with both properties obtained from the ActorB and ActorC
log.info("Got property from ActorB: '{}' and property from ActorC: '{}'", actorBProp, actorCProp);
return command;
});
return Behaviors.same();
}
private record WrappedResponse(String response) implements ActorA.Command { }
}
Is this correct, or is there a better way? Most of Akka is pretty elegant, so I really feel like I am missing something important and I am hoping that someone with more experience can point me in the right direction. I would be very interested in hearing how experienced akka users approach this. Thanks in advance!
Upvotes: 0
Views: 112
Reputation: 10652
Personally, I would refrain from using futures for something like this. Assuming this is something that happens often in your process... You have actors, you have behaviours, so this gives you quite some options by staying in the system...
Anonymous actor
ActorA spawns an anoynmous actor, which gets an ActorRef
to ActorA. Then an ActorRef
of this anonymous actor is put into the messages to ActorB and ActorC, let's call it "replyTo", so both will reply to it.
The anonymous actor will simply take messages from ActorB and ActorC and, after both are received (you may also want to do timeouts here, etc.) sends the final messages back the ActorA, which then can react to it (this step is optional, perhaps also the anonymous actor can do all the work already). This also works fine if you have not two but a dynamic number of messages you are waiting for.
Behaviour
Alternatively, you could simply add two more different behaviours into ActorA: "replyFromBReceived" and "replyFromCReceived", in which you only wait for a reply from the missing actor (again, tiemout, etc. might be nice). As soon as you get the missing reply, you process it and go back to your initial behavior. You might want to add a stash if you need to reject (temporarily) messages that do not fit there.
Upvotes: 1