To implement your own custom actor in Akka (Java binding) you extend the UntypedActor
base class. This requires you to define your own onReceive(...)
method:
@Override
public void onReceive(Object message) {
// TODO
}
The problem at hand is determining a message handling strategy that enables actors to handle multiple types of messages. One strategy would be to use reflection/types. The problem here is that:
message
parameter and prevents us from being able to pass anything dynamic or meaningfulExample of an empty shell class:
public class EmptyShellMessage { }
Then in the onReceive
method would look like:
@Override
public void onReceive(Class<?> message) {
if(message.isAssignableFrom(EmptyShellMessage.class)) {
// TODO
} else {
// TODO
}
}
So not only do we create an otherwise-useless class, but since the Object message
is now being used to convery what class/type the message is, we can't use it to contain any more info, especially dynamic/runtime info that another actor might want to pass it.
Sometimes I see a variation of this:
@Override
public void onReceive(Object message) {
if(message instanceof FizzEvent) {
// TODO
} else {
// TODO
}
}
But here we're using instanceof
which is considered by many to be a huge anti-pattern (just google "instanceof antipattern").
Then we have enums:
public enum ActorMessage {
FizzEvent,
BuzzEvent,
FooEvent,
BarEvent
}
Now onReceive
looks like:
@Override
public void onReceive(ActorMessage message) {
if(message.equals(ActorMessage.FizzEvent)) {
// TODO
} else {
// TODO
}
}
The problem here is that we may have a large actor system with hundreds or even thousands of different event/message types to handle. This enum becomes large and difficult to maintain. It also has the same problem as the reflection strategy above where it prevents us from sending any dynamic info between actors.
The last thing I can think of is to use Strings:
@Override
public void onReceive(String message) {
if(message.equals("FizzEvent")) {
// TODO
} else {
// TODO
}
}
But I hate this. Period. End of sentence.
So I ask: am I missing something obvious here, another strategy perhaps? How are Java/Akka apps supposed to be handle large numbers of event/message types, and specify which one they are handling in the onReceive
method?
1) Akka Actor tell() Method It works on "fire-forget" approach. You can also use ! (bang) exclamation mark to send message. This is the preferred way of sending messages.
Companion object ActorSystem An actor system is a hierarchical group of actors which share common configuration, e.g. dispatchers, deployments, remote capabilities and addresses. It is also the entry point for creating or looking up actors. There are several possibilities for creating actors (see akka.
Behind the scenes Akka will run sets of actors on sets of real threads, where typically many actors share one thread, and subsequent invocations of one actor may end up being processed on different threads.
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.
No you are not missing anything. I am not a fan either. In Scala it is a bit better because the onReceive method can be swapped out to simulate the changing state of a protocol and it uses partial functions which are a little nicer than if/elseif/else... but it is still icky. It is nicer in Erlang, which is where this model originated but that said given the limitations faced by the akka team, they made the right design choices and did an excellent job.
An alternative strategy is to perform a double dispatch. So pass the command into the actor to be actions, or lookup in a map a handler for the message. Akka agents are essentially the former, and when used to their strength are quite nice. But in general a double dispatch just adds complexity, so for most cases one just has to get used to the standard approach. Which in java means either if instanceof or a switch statement.
An example of double dispatch (psuedo code), included here for completeness, to aid understanding. As an approach it comes with health warnings, so I reiterate; the standard approach is standard for a reason and that one should use that 99% of the time.
onReceive( msg ) {
msg.doWork()
}
The problem with this approach is that now the message needs to know how to process itself, which is dirty; it causes tight coupling and can be fragile. Yuck.
So we can go with using a lookup for handlers (command pattern stylie)
val handlers = Map( "msgid1"->Handler1, "msgid2->Handler2 )
onReceive( msg ) {
val h = handlers(msg.id)
h.doWork( msg )
}
the problem here is that now it is unclear what the commands are and following the code through involves jumping around more. But there are times that this is worth while.
As pointed out by Roland, care must be taken when passing around a reference to the actor itself. The examples above do not fall foul of that problem, but it would be an easy temptation to do.
I strongly recommend against passing around the Actor’s this
reference because that easily invites you to pass it across execution context boundaries, which can happen in entirely inconspicuous ways. The other problem is that it requires the message type to know how the actor wants to handle it, which is the exact opposite of how message-passing should decouple different entities: the Actor must choose how to deal with the message, not the other way around.
Actors are dynamic entities, they can receive inputs in unforeseeable sequence (which is at the core of the model), and therefore we must use a dynamic language feature to implement them. instanceof
is part of the language to facilitate the runtime discovery of message types. Whether or not it is abused in other contexts, and independently of who calls it an anti-pattern, it is exactly the right tool for this job. Actor implementations in other languages—including the venerable Erlang—use pattern matching to achieve this same effect, and pattern matching is exactly the same thing as instanceof
tests (plus more convenient parameter extraction).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With