4
votes

I'm new to Akka and I'm exited about this technology, but I can't understand how to cook actors without business logic. Let's say I need to make a game. The game has limits on players and after choosing winner it can't be chosen more.

public class Game {

    private List<String> participants;
    private int maxParticipants;
    private String winner;
    private boolean ended;

    public int getMaxParticipants() {
        return maxParticipants;
    }

    public int getParticipantsSize() {
        return participants.size();
    }

    public void addParticipant(String participant) {

        participants.add(participant);
    }

    public boolean isEnded() {
        return ended;
    }

    public void chooseWinner() {
        if (ended) {
            // do some stuff
        }
        winner = participants.get(randomNumber);
        ended = true;
    }

}

And actor class:

public class GameActor extends UntypedActor {

    private Game game = new Game();

    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof AddParticipant) {
            // For example there can't be more then two participants.
            // I want to have this "if" inside my Game class, not here
            if (game.getParticipantsSize() >= game.getMaxParticipants()) {
                // do something about it
            }
            game.addParticipant(message.getParticipant());
        }else if (message instanceof ChooseWinner){
            // We can't choose winner after game ended. I don't want to have this logic here
            game.chooseWinner();
        }
    }
}

Right now I see several approaches. In simple cases they all could work, but they are very limited:

  1. Rise exceptions. Works only in negative cases. If everything is OK I don't know what to do next. Also catch blocks is ugly and I don't want to maintain GameFullException, GameEndedException and so on.

  2. Return some value. Like boolean in addParticipant if it was successful. Either limited usage or another layer of ifelse for return value.

  3. Game class can rise events and my actor can be subscribed to it.

Like this:

public class Game {

    private List<Listener> listeners = new ArrayList<>();

    public void addListener(Listener listener) {
        listeners.add(listener);
    }

    public void riseEvent(Event event) {
        listeners.forEach(l->l.handle(event));
    }

}

And single listener is the actor:

public class GameActor extends UntypedActor {

    private Game game = new Game();

    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof AddParticipant) {
            game.addParticipant(message.getParticipant());
        }else if (message instanceof ChooseWinner){
            game.chooseWinner();
        }else if( message instanceof Event){
            // do something with actor state if needed
        }
    }

    public void handle(Event event) {
        self().tell(event, self());
    }
}

The third one seems the most interesting and powerful for me, but it just seems wrong that my model here has one subscriber and it's an actor which in turn sends raised by model events to itself. Of course this game class is just an illustration(and I'm not sure if it's a good illustration of my problem) and such simple logic could be done in actor and it's OK, but I'm interested in principle of how to decouple business logic from actors, because I don't think actors are good place for business logic for many reasons. I'm interested both in Scala and Java. Some examples would be great.

2

2 Answers

3
votes

Why should your cook actors without bussiness logic? Encapsulating states is excactly what actors a good at. In you example, I would just drop the Game class, and put its fields inside the GameActor like this:

public class GameActor extends UntypedActor {

private List<String> participants;
private int maxParticipants;
private String winner;
private boolean ended;

@Override
public void onReceive(Object message) throws Exception {
    if (message instanceof AddParticipant) {
        //In my opinion, its perfectly valid to have this here
        if (this.participants.size() >= this.maxParticipants) {
            // do something about it
        }
        this.participant.add(message.getParticipant());
    }else if (message instanceof ChooseWinner){
        //again I why not put the logix here?
        if (this.ended)
            // Handle this error somehow.
        else
           this.winner = messag.getWinner();
           this.ended = true;
    }
}

}

But if your want to split this up into two classes you still can, but they should both be actors. You could as example seperate it into an actor holding the state of the game (a bit like your game class, but this should also be an actor) and an actor performing the logic of what should happen of side effects when the state changes.

The actor holding the state could notify the logic actor in different ways. The most simple would be to just reply to sender when state has changes.This would work if its always the actor that sends the state change request, that should also do something when the state is changes. If however there are more actors that should react to changes in game states, it could instead send a message to akkas eventstream, and all actors that has subscribed to that would then receive the event.

1
votes

But surely actors are the place to put business logic. Actors are the main business handling entity of concurrent systems. In Erlang, Actors are simply programs that run when they receive messages. If you want a central entity to handle the content of the messages, you have to delegate the messages from the actors anyways.

Another comment I'd like to make is on your rule 3 idea. This is something I'd strongly recommend against, since this is the exact opposite idea of what Akka is supposed to do. If your Game needs to send messages to your GameActor, you should make your Game class extend Actor itself and then send messages through the Akka logic.

In other words, the main idea with Akka is simply that it makes it easy for business actors to communicate concurrently with each other. Separating business logic from your actors is then the process of deconstructing Akka to something not very useful ;-)