smeeb
smeeb

Reputation: 29477

Orchestrating child actors in Akka

I am brand new to Akka and am wondering how I should be handling actors that delegate work out to other (child) actors, but where:

  1. Some of these child actors must be engaged in a particular order; and
  2. Some of these child actors can be engaged in any order and can truly execute asynchronously to what the main/parent actor is doing

Say I have the following child actors (doesn’t matter what they do):

// Grovy pseudo code
class ActorA extends UntypedActor {
    @Override
    void onReceive(Object message) {
        if(message instanceof RunActorA) {
            …
    }
}
class ActorB extends UntypedActor {
    @Override
    void onReceive(Object message) {
        if(message instanceof RunActorB) {
            …
    }
}
class ActorC extends UntypedActor {
    @Override
    void onReceive(Object message) {
        if(message instanceof RunActorC) {
            …
        }
    }
class ActorD extends UntypedActor {
    @Override
    void onReceive(Object message) {
        if(message instanceof RunActorD) {
            …
        }
    }

And say I have the following parent actor that invokes them:

class MasterParent extends UntypedActor {
    ActorRef actorA
    ActorRef actorB
    ActorRef actorC
    ActorRef actorD

    MasterParent(ActorRef actorA, ActorRef actorA, ActorRef actorA, ActorRef actorA) {
        super()

        this.actorA = actorA
        this.actorB = actorB
        this.actorC = actorC
        this.actorD = actorD
    }

    @Override
    void onReceive(Object message) {
        if(message instanceof ProcessData) {
            ProcessData pData = message as ProcessData
            Widget widget = pData.widget
            RunActorA runA = new RunActorA(widget)

            actorA.tell(runA, self)

            // Somehow obtain a result from A, perhaps an “ResultOfA” object.
            ResultOfA resultA = ??? // get from ActorA

            RunActorB runB = new RunActorB(resultA)
            RunActorC runC = new RunActorC(resultA)

            actorB.tell(runB, self)
            actorC.tell(runC, self)

            // Somehow wait until both B and C return a result, say, ResultOfB and ResultOfC, respectively.
            ResultOfB resultB = ??? // get from ActorB
            ResultOfC resultC = ??? // get from ActorC

            RunActorD runD = new RunActorD(resultB, resultC)

            actorD.tell(runD, self)
        }
    }
}

As you can see:

In other words, when a ProcessData message is received by MasterParent:

  1. Run ActorA and wait for it to return a result; then
  2. Run both ActorB and ActorC and wait for results from both of them; then
  3. Run ActorD

My main question is: How do I achieve this (Java pseudo-code or examples greatly appreciated!)?

Second, and arguable more importantly, since I’m so new to actors: Is this type of parent-child orchestration “normal” in Akka? Or, do Akka best practices typically dictate that all children run asynchronously and without imposed sequence/order/orchestration?

Upvotes: 0

Views: 752

Answers (1)

thwiegan
thwiegan

Reputation: 2173

First of all, yes since the MasterParent is delegating work to the other actors, it makes sense to have them as child actors.

Also your idea of ResultOfA wasn't too far away from.

This is one way you could do it:

ResultOfB resultB = null;
ResultOfC resultC = null;

@Override
void onReceive(Object message) {
    if(message instanceof ProcessData) {
        ProcessData pData = message as ProcessData

        resultB = null;
        resultC = null;

        Widget widget = pData.widget
        RunActorA runA = new RunActorA(widget)

        actorA.tell(runA, self)
    } else if (message instanceof ResultOfA) {
        ResultOfA resultA = message as ResultOfA

        actorB.tell(resultA)
        actorC.tell(resultA)
    } else if (message instanceof ResultOfB) {
        resultB = message as ResultOfB

        if (resultC != null) {
            actorD.tell(composedResultBAndC)
        }
    } else if (message instanceof ResultOfC) {
        resultC = message as ResultOfC

        if (resultB != null) {
            actorD.tell(composedResultBAndC)
        }
    } else if (message instanceof ResultOfD) {
        //Do stuff with the result
    }
}

Where every child actor would do sender().tell(resultX) so that the master receives the result.

ResultB and ResultC are both needed, so I save some state within the actor, which is set to null on every new instance of ProcessData, so it is not possible to send multiple ProcessData to one MasterParent in this case.

If you want to send multiple ProcessData messages to one MasterParent without waiting, you would need to keep it free of state and therefore could employ the Ask Pattern on actorB and actorC and resolve the returning futures together. So something like:

Future futureA = actorB.ask(resultA)
Future futureB = actorC.ask(resultA)

And then compose them and register a callback where you send a request to actorD.

This code pseudo codish, so not runnable.

Upvotes: 1

Related Questions