Functional Interface

Functional Interface #

In this section we discuss an alternative API for creating behaviors for actors. We use the same tests as before and adjust the corresponding behavior classes to the functional actor API.

Receiving messages #

Here is an adjusted definition for the Terminator behaviour.

static class Terminator {
    static Behavior<Object> create() {
        return Behaviors.receiveMessage(msg -> Behaviors.stopped());
    }
}

The class no longer extends AbstractBehavior and uses Behaviours.receiveMessage to create an appropriate behaviour.

The adjusted create method for the Bouncer behavior also uses receiveMessage.

static Behavior<Ping> create() {
    return Behaviors.receiveMessage(ping -> {
        ping.sender.tell(new Pong());
        return Behaviors.same();
    });
}

We cannot return this from the message handler and instead use Behaviors.same.

Local state #

The conversion of the Calculator is interesting because it has local state for the total value. In the functional style, local state can be passed in arguments to methods creating behaviors.

static Behavior<Cmd> create(int total) {
    return Behaviors.receive(Cmd.class)
        .onMessage(GetTotal.class, msg -> {
            msg.sender.tell(total);
            return Behaviors.same();
        })
        .onMessage(AddFraction.class, msg -> {
            return create(total + msg.numerator / msg.denominator);
        })
        .build();
}

Note the recursive call to create with a new total value in a message handler. To register multiple handlers for different message types we can the builder pattern with Behaviors.receive.

Actor context #

If we need to access the actor context, for example to spawn child actors, we can use Behaviors.setup. Consider the create method for the Computer behavior.

static Behavior<Request> create() {
    return Behaviors.setup(ctx -> {
        ActorRef<Calculator.Cmd> calc = ctx.spawnAnonymous( //
            Calculator.create(0));
        ctx.watch(calc); // death pact
        return computer(calc);
    });
}

The method computer expects a reference to a calculator as argument representing its local state and implements the Computer behavior.

private static Behavior<Request> computer(ActorRef<Calculator.Cmd> calc) {
    return Behaviors.receive(Request.class)
        .onMessage(Forward.class, msg -> {
            calc.tell(msg.cmd);
            return Behaviors.same();
        })
        .onMessage(Compute.class, msg -> {
            calc.tell(new Calculator.AddFraction(100, msg.arg));
            return Behaviors.same();
        })
        .build();
}

Task: Refactor echo loop #

Modify the definition of EchoLoop to use the functional instead of the object-oriented API for the root behavior of the actor system.