ScalaでRemote Actorのシンプルな例


Remote Actorを使いプロセスやネットワークを超えて、Actorを統合する方法をサンプルを示しながらご紹介します。

こちらのサンプルの動作する完全版はGitHubを御覧ください。[suin/akka-sample-remote-scala](https://github.com/suin/akka-sample-remote-scala)

## Step 1: プロジェクトを作成する

まず、build.sbtを作成します。こちらのサンプルはわけあって古いバージョンのakkaとScalaを使っていますが、最新のバージョンを指定していただいてもいいかと思います。

```scala:build.sbt
name := """akka-sample-remote-scala"""

version := "2.3.10"

scalaVersion := "2.10.4"

libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-actor" % "2.3.3",
  "com.typesafe.akka" %% "akka-remote" % "2.3.3"
)
```

## Step 2: 共有するオブジェクトを定義する

リモートとローカルで送受信するオブジェクトを定義します。

```scala:common/package.scala
package suin

package object common {
  case object Hello
}
```

## Step 3: リモートのActor Systemを実装する

リモートのActor Systemをコーディングします。

```scala:remote/RemoteApp.scala
package suin.remote

import java.io.File

import akka.actor.{ActorLogging, Props, ActorSystem, Actor}
import com.typesafe.config.ConfigFactory
import suin.common.Hello

object RemoteApp extends App {
  val configFile = getClass.getClassLoader.getResource("remote_application.conf").getFile
  val config = ConfigFactory.parseFile(new File(configFile))
  val system = ActorSystem("remote-system", config)
  val remote = system.actorOf(Props[RemoteActor], name="remote")
  system.log.info("Remote is ready")
}

class RemoteActor extends Actor with ActorLogging {
  override def receive: Receive = {
    case Hello =>
      log.info("Remote received {} from {}", Hello, sender)
      sender ! Hello
    case msg: String =>
      log.info("Remote received {} from {}", msg, sender)
      sender ! "hi"
    case any =>
      log.info("Remote received unknown message {} from {}", any, sender)
  }
}
```

次に、リモートの設定ファイルを作成します。

```nginx:remote-application.conf
akka {
  loglevel = "INFO"
  actor {
    provider = "akka.remote.RemoteActorRefProvider"
  }
  remote {
    enabled-transports = ["akka.remote.netty.tcp"]
    netty.tcp {
      hostname = "127.0.0.1"
      port = 5150
    }
    log-sent-messages = on
    log-received-messages = on
  }
}
```

## Step 4: ローカルのActor Systemを実装する

ローカルのActor Systemをコーディングします。

```scala:local/LocalApp.scala
package suin.local

import java.io.File

import akka.actor.{ActorLogging, Props, ActorSystem, Actor}
import com.typesafe.config.ConfigFactory
import suin.common.Hello

class LocalActor extends Actor with ActorLogging {
  @throws[Exception](classOf[Exception])
  override def preStart(): Unit = {
    val remoteActor = context.actorSelection(LocalApp.config.getString("app.remote-system.remote-actor"))
    log.info("Remote actor is {}", remoteActor)
    remoteActor ! "hi"
    remoteActor ! Hello
  }

  override def receive: Receive = {
    case msg: String =>
      log.info("Client received {} from {}", msg, sender)
    case Hello =>
      log.info("Client received {} from {}", Hello, sender)
    case any =>
      log.info("Client received unknown message {} from {}", any, sender)
  }
}

object LocalApp extends App {
  val configFile = getClass.getClassLoader.getResource("local_application.conf").getFile
  val config = ConfigFactory.parseFile(new File(configFile))
  val system = ActorSystem("client-system", config)
  val localActor = system.actorOf(Props[LocalActor], name = "local")
}
```

ローカルの設定ファイルを作成します。

```nginx:local_application.conf
akka {
  loglevel = "INFO"
  actor {
    provider = "akka.remote.RemoteActorRefProvider"
  }
  remote {
    enabled-transports = ["akka.remote.netty.tcp"]
    netty.tcp {
      hostname = "127.0.0.1"
      port = 0 # any free port
    }
    log-sent-messages = on
    log-received-messages = on
  }
}

app.remote-system.remote-actor = "akka.tcp://remote-system@127.0.0.1:5150/user/remote"
```

## Step 5: ビルドして実行する

ターミナルを2つ立ち上げます。

ひとつのターミナルでリモートのActor Systemを起動します。

```console
% ./activator run
[info] Loading project definition from /Users/suin/Desktop/akka-sample-remote-scala/project
[info] Set current project to akka-sample-remote-scala (in build file:/Users/suin/Desktop/akka-sample-remote-scala/)

Multiple main classes detected, select one to run:

 [1] suin.local.LocalApp
 [2] suin.remote.RemoteApp

Enter number: 2
```

もうひとつのターミナルでローカルのActor Systemを起動します。

```console
% ./activator run
[info] Loading project definition from /Users/suin/Desktop/akka-sample-remote-scala/project
[info] Set current project to akka-sample-remote-scala (in build file:/Users/suin/Desktop/akka-sample-remote-scala/)

Multiple main classes detected, select one to run:

 [1] suin.local.LocalApp
 [2] suin.remote.RemoteApp

Enter number: 1
```

実行結果がそれぞれコンソールに表示されます。

```text:リモート
[info] Running suin.remote.RemoteApp
[INFO] [06/21/2015 17:02:15.641] [run-main-0] [Remoting] Starting remoting
[INFO] [06/21/2015 17:02:15.799] [run-main-0] [Remoting] Remoting started; listening on addresses :[akka.tcp://remote-system@127.0.0.1:5150]
[INFO] [06/21/2015 17:02:15.801] [run-main-0] [Remoting] Remoting now listens on addresses: [akka.tcp://remote-system@127.0.0.1:5150]
[INFO] [06/21/2015 17:02:15.814] [run-main-0] [ActorSystem(remote-system)] Remote is ready
[INFO] [06/21/2015 17:03:21.080] [remote-system-akka.actor.default-dispatcher-3] [akka.tcp://remote-system@127.0.0.1:5150/user/remote] Remote received hi from Actor[akka.tcp://client-system@127.0.0.1:59552/user/local#167765235]
[INFO] [06/21/2015 17:03:21.081] [remote-system-akka.actor.default-dispatcher-3] [akka.tcp://remote-system@127.0.0.1:5150/user/remote] Remote received Hello from Actor[akka.tcp://client-system@127.0.0.1:59552/user/local#167765235]
```

```text:ローカル
[info] Running suin.local.LocalApp
[INFO] [06/21/2015 17:03:20.763] [run-main-0] [Remoting] Starting remoting
[INFO] [06/21/2015 17:03:20.898] [run-main-0] [Remoting] Remoting started; listening on addresses :[akka.tcp://client-system@127.0.0.1:59552]
[INFO] [06/21/2015 17:03:20.900] [run-main-0] [Remoting] Remoting now listens on addresses: [akka.tcp://client-system@127.0.0.1:59552]
[INFO] [06/21/2015 17:03:20.911] [client-system-akka.actor.default-dispatcher-2] [akka.tcp://client-system@127.0.0.1:59552/user/local] Remote actor is ActorSelection[Anchor(akka.tcp://remote-system@127.0.0.1:5150/), Path(/user/remote)]
[INFO] [06/21/2015 17:03:21.092] [client-system-akka.actor.default-dispatcher-2] [akka.tcp://client-system@127.0.0.1:59552/user/local] Client received hi from Actor[akka.tcp://remote-system@127.0.0.1:5150/user/remote#-68561685]
[INFO] [06/21/2015 17:03:21.093] [client-system-akka.actor.default-dispatcher-2] [akka.tcp://client-system@127.0.0.1:59552/user/local] Client received Hello from Actor[akka.tcp://remote-system@127.0.0.1:5150/user/remote#-68561685]
```


参考: [A Simple Akka Remote example](http://blog.madhukaraphatak.com/simple-akka-remote-example/)