Akka / Java: Handling multiple message types inside a user actor?

To implement your own custom actor in Akka (Java binding), you extend the base class UntypedActor . This requires that you define your own onReceive(...) method:

 @Override public void onReceive(Object message) { // TODO } 

The problem is defining a message processing strategy that allows entities to process multiple types of messages. One strategy would be to use reflections / types. The problem here is that:

  • This forces us to create empty โ€œshell classesโ€ that not only provide semantic meaning for the message (see below); and
  • It runs the message parameter and does not allow us to pass anything dynamic or meaningful

An example of an empty shell class:

 public class EmptyShellMessage { } 

Then in the onReceive method onReceive will look like this:

 @Override public void onReceive(Class<?> message) { if(message.isAssignableFrom(EmptyShellMessage.class)) { // TODO } else { // TODO } } 

Thus, we are creating not only a useless class, but since the Object message now used to encode that class / message type, we cannot use it to contain more information, especially dynamic / that can be conveyed by another actor.

Sometimes I see a variation of this:

 @Override public void onReceive(Object message) { if(message instanceof FizzEvent) { // TODO } else { // TODO } } 

But here we use instanceof , which is considered by many to be a huge anti-pattern (just google "instanceof antipattern").

Then we have the listings:

 public enum ActorMessage { FizzEvent, BuzzEvent, FooEvent, BarEvent } 

Now onReceive looks like this:

 @Override public void onReceive(ActorMessage message) { if(message.equals(ActorMessage.FizzEvent)) { // TODO } else { // TODO } } 

The problem is that we can have a large system of actors with hundreds or even thousands of different types of events / messages. This listing becomes large and difficult to maintain. It also has the same problem as the reflection strategy above, where it prevents us from transmitting any dynamic information between the participants.

The last thing I can think of is to use the lines:

 @Override public void onReceive(String message) { if(message.equals("FizzEvent")) { // TODO } else { // TODO } } 

But I hate that. Period. The end of the sentence.

So I ask: did I miss something obvious here, maybe a different strategy? How should Java / Akka applications process a large number of event / message types and indicate which one they process in the onReceive method?

+6
source share
3 answers

No, you did not miss anything. I'm not a fan either. In Scala, this is a little better, because the onReceive method can be swapped to simulate the changing state of the protocol, and it uses partial functions that are a bit nicer than if / elseif / else ... but this is still not good. At Erlang, this is better, and this is where this model came about, but due to the limitations that the akka team faced, they made the right design choice and did an excellent job.

An alternative strategy is to double submit. So, pass the command to the actor in action or find the handler for the message in the map. Assault agents are essentially the first, and when they are used to their strength, they are quite pleasant. But overall, double-sending just adds complexity, so for most cases, you just need to get used to the standard approach. Which in java means either if instanceof or the switch statement.


A double submit example (psuedo code) is included here for completeness to help understand. As an approach, it refers to health warnings, so I repeat; The standard approach is standard for a reason and that you should use it 99% of the time.

 onReceive( msg ) { msg.doWork() } 

The problem with this approach is that now the message should know how to handle itself, which is dirty; It causes tight grip and can be fragile. Ugh.

So, we can use the search for handlers (command template styles)

 val handlers = Map( "msgid1"->Handler1, "msgid2->Handler2 ) onReceive( msg ) { val h = handlers(msg.id) h.doWork( msg ) } 

the problem here is that it is now unclear which teams follow the code, including jumping around the bigger one. But there are times when it's worth it.

As Roland pointed out, caution should be exercised when distributing links to the actor himself. The above examples do not defile this problem, but it would be an easy temptation to do.

+6
source

I highly recommend dropping the Actors this link because it easily invites you to pass it across the boundaries of the execution context, which can happen completely unnoticed. Another problem is that it requires the message type to know how the actor wants to process it, which is the exact opposite of how message passing should unleash different entities: the actor must choose how to handle the message, and not vice versa.

Actors are dynamic objects, they can receive input in an unexpected sequence (which underlies the model), and therefore we must use a dynamic language function to implement them. instanceof is part of the language to facilitate the detection of message types. Regardless of whether they are abused in other contexts, and regardless of who calls it an anti-pattern, this is definitely the right tool for this job. Actors' implementations in other languages, including the venerable example of using Erlang to achieve the same effect, as well as pattern matching, are the same as instanceof tests (plus more convenient parameter extraction).

0
source

I created a simple DSL-compliant template for Java 8 that can be useful for Akka actors: https://github.com/strangepleasures/matchmetender

 public class MatchingActor extends UntypedActor { private LoggingAdapter log = Logging.getLogger(getContext().system(), this); public void onReceive(Object message) throws Exception { match(message, (Foo foo) -> log.info("received a Foo: " + foo), (Bar bar) -> log.info("received a Bar: " + bar), (Baz baz) -> log.info("received a Baz: " + baz), (unknown) -> unhandled(unknown) ); } } 
0
source

Source: https://habr.com/ru/post/975496/


All Articles