Steve Storck
Steve Storck

Reputation: 991

What is the right way for an Akka typed actor to ask two other actors for information?

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

Answers (1)

Florian Schaetz
Florian Schaetz

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

Related Questions