Akka/Java: Handling multiple message types inside a custom actor?
Asked Answered
R

4

8

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:

  1. It forces us to create empty "shell classes" that do nothing more than give semantic meaning for a message (see below); and
  2. It hogs the message parameter and prevents us from being able to pass anything dynamic or meaningful

Example 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?

Reparative answered 18/9, 2014 at 16:13 Comment(0)
F
6

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.

Facture answered 18/9, 2014 at 16:17 Comment(7)
Ahh, thanks @Chris K (+1) - nice to know I'm not the only one who feels like this is awkward. I'm not entirely familiar with double dispatch, but from my googling it seem like I would just write one onReceive overload for each message type my actor needs to handle. So if I want my actor to handle Fizz and Buzz messages, I would have 2 onReceive methods: (1) public void onReceive(Fizz fizz) and (2) public void onReceive(Buzz buzz). Is this correct? Thanks again!Reparative
@Reparative You also need an abstract method on your message that accepts your actor. In each concrete message implementation, this method passes the message to the actor. I was going to suggest this, but I don't like the compile-time dependency this creates from the messages to the actor. Wouldn't you like your messages to be unaware of the actor? I guess this could be avoided by declaring a protocol interface for your actor to implement (all of the overloaded onReceive methods), and then your messages could depend on that.Astronaut
Thanks @Astronaut (+1) - but I'm confused: why do I need an method on the message that accepts the actor? Won't Java call the correct overloaded onReceive method for me based on the runtime type of the message? If the message is a Fizz, the onReceive(Fizz) method should be invoked, no?Reparative
@Reparative I have added examples to the answer to elaborate on the two techniques that could be used. And as already discussed, they are a double edged sword. So only use if their complexity adds more than they obscure.Facture
@Reparative No, Java doesn't work that way. When Akka'as Actor class is compiled, it includes information about the onReceive(Object) method. At runtime, that's the only method that is considered as a target for the call. That's why double dispatch is required. When each concrete message type is compiled, the compiler resolves the most specific method applicable to that message type, and includes the information for that method into the compiled message class.Astronaut
Passing the Actor’s this reference around is a much larger problem and risk than just doing the obvious thing and using RTTI to find out about the message type (i.e. instanceof). Whether or not someone else thinks that that is an anti-pattern in some other context should not cloud your vision, it is the right thing to do here.Organist
@rolandkuhn, excellent point. The examples given did not fall foul of that, but it is a trap that is well worth being aware of. I have update the answer to draw attention to the risk of passing a ref to the actor around.Facture
O
1

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).

Organist answered 19/9, 2014 at 22:57 Comment(0)
S
0

I've created a simple pattern matching DSL for Java 8 which 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)
    );
  }
}
Stinko answered 22/3, 2017 at 13:7 Comment(0)
S
0

I agree on that the framework pushes you to do something not at all elegant, and I don't dirty my fingers writting such code. So my approach is this. 1) An Interface:

public interface IAnswerable {
    Object getAnswer();
}

2) The actor:

@Override
public void onReceive(Object message) throws Throwable {
    IAnswerable iAnswerable = (IAnswerable) message;
    getSender().tell(iAnswerable.getAnswer(), getSelf());
}

3) Classes that I use as messages:

public class MyClass implements IAnswerable {

@Override
public Object getAnswer() {
    // Whatever you need to do here
    return null;
}

}

This is a raw approach but you get the idea. It's elegant and readable, and it prevents the actor of getting to big.

Hope it helps ;)

Segregation answered 15/3, 2019 at 11:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.