Typesafe Akka Remote Sampleの図解 - 1/2 CreationAppliation編
TypeSafeのAkka Remoteのサンプルがなんともわかりにくい気がするので、ここで図解してみることにしてみました。
TypeSafeのAkka Remote Samples with Scalaには2つのサンプルアプリケーションが含まれている
一つはCreationApplication, もう一つはLookupApplicationです。これらはコードに共通部分もありますが、別々のアプリケーションです。
この記事ではCreationAppliationのみを解説します。LookupApplicationについては別記事にしようと思います。
メッセージの型としてのcase class
Akkaでよく使われるcase classをメッセージの型として使う方法です。足し、引き、掛け、割り算に相当する以下の4つが定義されています。
// sample/remote/calculator/MathOp.scala
trait MathOp
final case class Add(nbr1: Int, nbr2: Int) extends MathOp
final case class Subtract(nbr1: Int, nbr2: Int) extends MathOp
final case class Multiply(nbr1: Int, nbr2: Int) extends MathOp
final case class Divide(nbr1: Double, nbr2: Int) extends MathOp
それぞれに対する結果型も用意されています。以下で見るようにActorはこれらの型のメッセージをやり取りして、計算の入力と結果を受け渡します。
// sample/remote/calculator/MathOp.scala
trait MathResult
final case class AddResult(nbr: Int, nbr2: Int, result: Int) extends MathResult
final case class SubtractResult(nbr1: Int, nbr2: Int, result: Int) extends MathResult
final case class MultiplicationResult(nbr1: Int, nbr2: Int, result: Int) extends MathResult
final case class DivisionResult(nbr1: Double, nbr2: Int, result: Double) extends MathResult
CalculatorActorは計算入力を受け取って結果を返す、CreationActorはCalculatorActorを生成して、計算を行わせる
CalculatorActor
CalculatorActorの実装は以下の通りです。
// sample/remote/calculator/calculatorActor.scala
class CalculatorActor extends Actor {
def receive = {
case Add(n1, n2) =>
println("Calculating %d + %d".format(n1, n2))
sender() ! AddResult(n1, n2, n1 + n2)
case Subtract(n1, n2) =>
println("Calculating %d - %d".format(n1, n2))
sender() ! SubtractResult(n1, n2, n1 - n2)
case Multiply(n1, n2) =>
println("Calculating %d * %d".format(n1, n2))
sender() ! MultiplicationResult(n1, n2, n1 * n2)
case Divide(n1, n2) =>
println("Calculating %.0f / %d".format(n1, n2))
sender() ! DivisionResult(n1, n2, n1 / n2)
}
}
例えばMultiply型のメッセージを受け取ったときは、その結果であるMultiplicationResult型のメッセージを送信元"sender"に投げ返します。
CreationActor
CreationActorの方は、
// sample/remote/calculator/CreationActor .scala
class CreationActor extends Actor {
def receive = {
case op: MathOp =>
val calculator = context.actorOf(Props[CalculatorActor])
calculator ! op
case result: MathResult => result match {
case MultiplicationResult(n1, n2, r) =>
printf("Mul result: %d * %d = %d\n", n1, n2, r)
context.stop(sender())
case DivisionResult(n1, n2, r) =>
printf("Div result: %.0f / %d = %.2f\n", n1, n2, r)
context.stop(sender())
}
}
}
計算の入力(MathOp型のメッセージ)を受け取ると、CalculatorActorを生成します。
val calculator = context.actorOf(Props[CalculatorActor])
そしてその生成したCalculatorActorに計算入力(MathOp型のメッセージ)を投げて
calculator ! op
MathResult型のメッセージを受け取ります。
def receive = {
...
case result: MathResult => result match {
case MultiplicationResult(n1, n2, r) =>
...
case DivisionResult(n1, n2, r) =>
...
}
以上の手順が終わったら、生成したCalculatorActorを以下のコードによって停止します。sender()となっていますが、これはMathResultのsenderなので、すなわちCalculatorActorです。
context.stop(sender())
CreateApplication
最後にアプリケーションの説明です。なんだかこのmain関数はややこしいのですが…、とにかくstartRemoteWorkerSystem()とstartRemoteCreationSystem()という二つの関数を走らせるだけです。
「コマンドライン引数」のCalculatorWorkerとCreationを渡すと、2つの関数をの別のプロセスで走らせることができます。
sbt "runMain sample.remote.calculator.CreationApplication CalculatorWorker"
sbt "runMain sample.remote.calculator.CreationApplication Creation"
args.isEmpty、すなわちコマンドライン引数を渡さないと、一つのプロセスの中で2つの関数を走らせます。s
object CreationApplication {
def main(args: Array[String]): Unit = {
if (args.isEmpty || args.head == "CalculatorWorker")
startRemoteWorkerSystem()
if (args.isEmpty || args.head == "Creation")
startRemoteCreationSystem()
}
def startRemoteWorkerSystem(): Unit = {
...
}
def startRemoteCreationSystem(): Unit = {
...
}
}
startRemoteWorkerSystem()はActorSystemを初期化するだけで、それ自体は何もしません。
startRemoteCreationSystem()はもう一つの関数startRemoteWorkerSystem()ないで作られたActorSystem("CalculatorSystem")のしたにCreationActorを生成します。
これはAkka Remotingで説明されているRemote Creationになり、remotecreation.confに以下を指定することによって実現しています。
//remotecreation.conf
akka {
actor {
deployment {
"/creationActor/*" {
remote = "akka.tcp://CalculatorWorkerSystem@127.0.0.1:2552"
}
}
}
...
}
そしてあとはAkkaのスケジューラを使って、MultiplyとDivideメッセージを送り続け、CreationActorはCalculationActorを逐一生成、停止して計算を行っていきます。