Here's the pattern I have come across:
An actor A
has multiple children C1
, ..., Cn
. On receiving a message, A
sends it to each of its children, which each do some calculation on the message, and on completion send it back to A
. A
would then like to combine the results of all the children to pass onto another actor.
What would a solution for this problem look like? Or is this an anti-pattern? In which case how should this problem be approached?
Here is a trivial example which hopefully illustrates my current solution. My concerns are that is duplicates code (up to symmetry); does not extend very well to 'lots' of children; and makes it quite hard to see what's going on.
import akka.actor.{Props, Actor}
case class Tagged[T](value: T, id: Int)
class A extends Actor {
import C1._
import C2._
val c1 = context.actorOf(Props[C1], "C1")
val c2 = context.actorOf(Props[C2], "C2")
var uid = 0
var c1Results = Map[Int, Int]()
var c2Results = Map[Int, Int]()
def receive = {
case n: Int => {
c1 ! Tagged(n, uid)
c2 ! Tagged(n, uid)
uid += 1
}
case Tagged(C1Result(n), id) => c2Results get id match {
case None => c1Results += (id -> n)
case Some(m) => {
c2Results -= id
context.parent ! (n, m)
}
}
case Tagged(C2Result(n), id) => c1Results get id match {
case None => c2Results += (id -> n)
case Some(m) => {
c1Results -= id
context.parent ! (m, n)
}
}
}
}
class C1 extends Actor {
import C1._
def receive = {
case Tagged(n: Int, id) => Tagged(C1Result(n), id)
}
}
object C1 {
case class C1Result(n: Int)
}
class C2 extends Actor {
import C2._
def receive = {
case Tagged(n: Int, id) => Tagged(C2Result(n), id)
}
}
object C2 {
case class C2Result(n: Int)
}
If you think the code looks god-awful, take it easy on me, I've just started learning akka ;)
In the case of many - or a varying number of - child actors, the ask pattern suggested by Zim-Zam will quickly get out of hand.
The aggregator pattern is designed to help with this kind of situation. It provides an Aggregator trait that you can use in an actor to perform your aggregation logic.
A client actor wanting to perform an aggregation can start an Aggregator based actor instance and send it a message that will kick off the aggregation process.
A new aggregator should be created for each aggregation operation and terminate on sending back the result (when it has received all responses or on a timeout).
An example of this pattern to sum integer values held by the actors represented by the Child class is listed below. (Note that there is no need for them to all be children supervised by the same parent actor: the SummationAggregator just needs a collection of ActorRefs.)
To use this SummationAggregator from your parent actor you could do:
and then handle AggregationResult somewhere in the parent's receive.