Defining Actors and messages
Each actor defines a type T
for the messages it can receive. Case classes and case objects make excellent messages since they are immutable and have support for pattern matching, something we will take advantage of in the Actor when matching on the messages it has received.
The Hello World Actors use three different messages:
Greet
: command sent to theGreeter
actor to greetGreeted
: reply from theGreeter
actor to confirm the greeting has happenedSayHello
: command to theGreeterMain
to start the greeting process
When defining Actors and their messages, keep these recommendations in mind:
-
Since messages are the Actor’s public API, it is a good practice to define messages with good names and rich semantic and domain specific meaning, even if they just wrap your data type. This will make it easier to use, understand and debug actor-based systems.
-
Messages should be immutable, since they are shared between different threads.
-
It is a good practice to put an actor’s associated messages as static classes in the AbstractBehavior’s class. This makes it easier to understand what type of messages the actor expects and handles.
-
It is a good practice obtain an actor’s initial behavior via a static factory method
Lets see how the implementations for Greeter
, GreeterBot
and GreeterMain
demonstrate these best practices.
The Greeter actor
The following snippet from the Greeter.java
implements the Greeter
Actor:
public class Greeter extends AbstractBehavior<Greeter.Greet> {
public static record Greet(String whom, ActorRef<Greeted> replyTo) {}
public static record Greeted(String whom, ActorRef<Greet> from) {}
public static Behavior<Greet> create() {
return Behaviors.setup(Greeter::new);
}
private Greeter(ActorContext<Greet> context) {
super(context);
}
@Override
public Receive<Greet> createReceive() {
return newReceiveBuilder().onMessage(Greet.class, this::onGreet).build();
}
private Behavior<Greet> onGreet(Greet command) {
getContext().getLog().info("Hello {}!", command.whom());
command.replyTo().tell(new Greeted(command.whom(), getContext().getSelf()));
return this;
}
}
This small piece of code defines two message types, one for commanding the Actor to greet someone and one that the Actor will use to confirm that it has done so. The Greet
type contains not only the information of whom to greet, it also holds an ActorRef
that the sender of the message supplies so that the Greeter
Actor can send back the confirmation message.
The behavior of the Actor is defined as the Greeter
AbstractBehavior with the help of the newReceiveBuilder
behavior factory. Processing the next message then results in a new behavior that can potentially be different from this one. The state can be updated by modifying the current instance as it is mutable. In this case we don’t need to update any state, so we return this
without any field updates, which means the next behavior is “the same as the current one”.
The type of the messages handled by this behavior is declared to be of class Greet
. Typically, an actor handles more than one specific message type and then there is one common interface
that all messages that the actor can handle implements
.
On the last line we see the Greeter
Actor send a message to another Actor, which is done using the tell
method. It is an asynchronous operation that doesn’t block the caller’s thread.
Since the replyTo
address is declared to be of type ActorRef<Greeted>
, the compiler will only permit us to send messages of this type, other usage will be a compiler error.
The accepted message types of an Actor together with all reply types defines the protocol spoken by this Actor; in this case it is a simple request–reply protocol but Actors can model arbitrarily complex protocols when needed. The protocol is bundled together with the behavior that implements it in a nicely wrapped scope—the Greeter
class.
The Greeter bot actor
package $package$;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.*;
public class GreeterBot extends AbstractBehavior<Greeter.Greeted> {
public static Behavior<Greeter.Greeted> create(int max) {
return Behaviors.setup(context -> new GreeterBot(context, max));
}
private final int max;
private int greetingCounter;
private GreeterBot(ActorContext<Greeter.Greeted> context, int max) {
super(context);
this.max = max;
}
@Override
public Receive<Greeter.Greeted> createReceive() {
return newReceiveBuilder().onMessage(Greeter.Greeted.class, this::onGreeted).build();
}
private Behavior<Greeter.Greeted> onGreeted(Greeter.Greeted message) {
greetingCounter++;
getContext().getLog().info("Greeting {} for {}", greetingCounter, message.whom());
if (greetingCounter == max) {
return Behaviors.stopped();
} else {
message.from().tell(new Greeter.Greet(message.whom(), getContext().getSelf()));
return this;
}
}
}
Note how this Actor manages the counter with an instance variable. No concurrency guards such as synchronized
or AtomicInteger
are needed since an actor instance processes one message at a time.
The Greeter main actor
A third actor spawns the Greeter
and the GreeterBot
and starts the interaction, creating actors and what spawn
does is discussed next.
package $package$;
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.*;
public class GreeterMain extends AbstractBehavior<GreeterMain.SayHello> {
public static class SayHello {
public final String name;
public SayHello(String name) {
this.name = name;
}
}
private final ActorRef<Greeter.Greet> greeter;
public static Behavior<SayHello> create() {
return Behaviors.setup(GreeterMain::new);
}
private GreeterMain(ActorContext<SayHello> context) {
super(context);
//#create-actors
greeter = context.spawn(Greeter.create(), "greeter");
//#create-actors
}
@Override
public Receive<SayHello> createReceive() {
return newReceiveBuilder().onMessage(SayHello.class, this::onSayHello).build();
}
private Behavior<SayHello> onSayHello(SayHello command) {
//#create-actors
ActorRef<Greeter.Greeted> replyTo =
getContext().spawn(GreeterBot.create(3), command.name);
greeter.tell(new Greeter.Greet(command.name, replyTo));
//#create-actors
return this;
}
}