Richard Imaoka's Blog

2017年より職業Scalaプログラマになった、リチャード・伊真岡のブログです。

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

f:id:richard-imaoka:20151105024105p:plain

それぞれに対する結果型も用意されています。以下で見るように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

f:id:richard-imaoka:20151105024107p:plain

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"に投げ返します。

f:id:richard-imaoka:20151105024114p:plain

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

f:id:richard-imaoka:20151105024118p:plain

そしてその生成したCalculatorActorに計算入力(MathOp型のメッセージ)を投げて

      calculator ! op

MathResult型のメッセージを受け取ります。

  def receive = {
  ...
    case result: MathResult => result match {
      case MultiplicationResult(n1, n2, r) =>
        ...
      case DivisionResult(n1, n2, r) =>
        ...
    }

f:id:richard-imaoka:20151105024121p:plain

以上の手順が終わったら、生成したCalculatorActorを以下のコードによって停止します。sender()となっていますが、これはMathResultのsenderなので、すなわちCalculatorActorです。

        context.stop(sender())

f:id:richard-imaoka:20151105024124p:plain

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を生成します。

f:id:richard-imaoka:20151105024126p:plain

これは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を逐一生成、停止して計算を行っていきます。 f:id:richard-imaoka:20151105024129p:plain