はじめに
Play Frameworkのactivatorについてるサンプル"Hello Akka!"のテキストを(かなり雑に)日本語に訳しました。各章がactivator uiに表示される説明テキストのページに対応してます。自分が勉強するついでに日本語訳したものでスーパー雑です!あとオリジナルのテキストにはJavaとScalaのそれぞれのソースに対する説明が併記されてますが、Java向けの説明は訳してません。
Source Code
Akkaは高度な並列分散、fault-tolerantでイベントドリブンなアプリケーションをJVM上に構築するツールキットとランタイムだ。AkkaはJavaとScalaどちらでも使える。Akkaのもっとも強力な機能はアクターモデルによる並列性で、このチュートリアルではそれについて学ぶ。
チュートリアルで扱うサンプルはとてもシンプルだ。サンプルではGreeterアクターを作る。Greeterアクターはgreeting文字列を持ち、2つのアクションに反応する。greeting文字列の設定とその文字列を表示するアクションだ。
Define our Messages
アクターはpublicなメソッドを持たず、その代わりにメッセージを扱う。メッセージの型は基本的にはAnyで任意の型をとれる。メッセージがアクターにとってのpublicなAPIなので、良い名前をつけるべきだ。
今回は以下の3つのメッセージを定義する。
- WhoToGreet: greeting文字列を設定する
- Greet: アクターに挨拶させる
- Greeting: greeting文字列を返す
メッセージがイミュータブルであることは非常に大事だ。ミュータブルなメッセージではアクター間で状態を共有する危険性が有り、それはアクターモデルの原則に背く。
Scalaではケースクラスでメッセージを定義する。ケースクラスはデフォルトでイミュータブルでパターンマッチをサポートするので良い。またserializableでもある。
case object Greet
case class WhoToGreet(who: String)
case class Greeting(message: String)
Define our Actor
アクターは振るまいと状態をカプセル化するという意味ではオブジェクト指向で、Akkaでの最小の実行単位だ。アクターは非常に軽量で、数百バイトしかメモリーを消費しないので一つのアプリケーションで何百万のアクターを作っても良い。互いに独立しておりイベントドリブンで、後述する位置透過性(location transparency)を持つことで並列でスケーラビリティが必要な問題を直感的に解決できる。
ScalaではアクターはActorトレイトをミックスインしreceiveメソッドを実装する。receiveメソッドでふるまい(受信したメッセージによるアクターの動作)を定義する。アクターは状態を持つことが多い。アクターモデルにより、アクター内部のミュータブルな状態へのアクセス、変更は完全にスレッドセーフになる。
ではGreeterアクターを作ろう。Greeterはgreetingを状態として持つ。receiveメソッドでWhoToGreetメッセージとGreetメッセージを受信した時のふるまいを定義しよう。
class Greeter extends Actor {
var greeting = ""
def receive = {
case WhoToGreet(who) => greeting = s"hello, $who"
case Greet => sender ! Greeting(greeting)
}
}
sender
や!
などは後で説明するので、今のところは気にしないで欲しい。アクターにはメッセージの型を特定するものがあるが、ここではメッセージはAny型のアクターを使う。Scalaではパターンマッチングにより非常に簡潔に書けている。
Javaでは、WhoToGreetとGreet以外の処理しないメッセージをunhandled
メソッドで処理するが、Scalaではそうなっていない。それはreceiveメソッドはいわゆる部分関数で、パターンマッチングに引っかからなかったことはメッセージが処理されなかったと認識され、Akkaは君のために自動でunhandled
メソッドを呼んでくれる。
Create our Actor
ここまで、アクターとメッセージの定義を行った。ではアクターのインスタンスをつくろう。Akkaではアクターを普通にnewして作ることはできない。その代わりにfactoryを使う。factoryからはアクターのインスタンス自体ではなく、アクターインスタンスを指すActorRefが得られる。
この間接性が大きな力と柔軟性をもたらす。ActorRefの参照先がローカルなプロセスでも、リモートマシンで走っていても同じように扱える(位置透過性)。ランタイムが動作中に最適化のためにアクターの場所を移動しても問題はない。もう一つのメリットとして、システムがエラーのあるアクターを終了し再起動する"let it crash"モデルにより自己回復性が得られる。
このfactoryはAkkaではActorSystemで、アクターのコンテナとして動作しアクターのライフサイクル等を管理する。そのような意味ではSpringフレームワークのBeanFactoryと同じようなものだ(適当な訳)。actorOfメソッドからもアクターを生成できる。このメソッドはPropsと呼ばれる設定オブジェクトと名前を引数に取る。Akkaではアクター(とアクターシステム)の名前は重要で、設定ファイルでその名前を参照するのでいい名前をつけよう。コードはこうなる。
val system = ActorSystem("helloakka")
val greeter = system.actorOf(Props[Greeter], "greeter")
これで動作しているGreeterアクターのインスタンスを得た。次はアクターとどう通信するかを学ぶ。
Tell the Actor (to do Something)
アクターとの通信はすべて非同期のメッセージパッシングで行われる。これがアクターをリアクティブでイベントドリブンにしている。アクターは何かしろと言われるまで何もしないので何かするようにメッセージを送ろう。メッセージの送信は非同期なので、送信側は受信側のアクターがメッセージを処理するのを待たない。かわりにアクターはメッセージを受信側のメールボックスに渡し、その後は受信側の反応を待つよりも有意義なことをやることができる。アクターのメールボックスは基本的にはキューで順番を持ち、これは同じアクターから複数メッセージが送られてもその送信の順序が保たれることを保証する。
アクターがメッセージを処理していない間はアクターは待ち状態になり、メモリ資源を消費しないで待っている。
ActorRefのtellメソッドでアクターにメッセージを送信できる。このメソッドはアクターのメールボックスにメッセージを置き即座に戻り値を返す。
greeter.tell(WhoToGreet("akka"), ActorRef.noSender)
greeter ! WhoToGreet("akka")
Scalaならbang operatorと呼ばれる!
のエイリアスを使える。
Replying to an Actor
The 'self' reference
一方向の通信でなく、要求-応答を行いたい時がある。一つの明らかなやり方はメッセージの一部として自分自身の参照を加え、受信側がその参照を使って送信側に応答を送るやり方だろう。これは明らかに一般的なやり方なので、Akkaは全ての送信メッセージに送信側の参照(アクターのActorRef)を付けるオプションを提供する。もしアクターからメッセージを送るなら自身のActorRefをself
で参照できる。(Javaではself
ではなくgetSelf
メソッドを使うように注意してほしい)
Scalaではこれが少し単純化される。Scalaは暗黙の引数(implicit parameter)という仕組みを持っていて、自動で、透過的にメソッドにパラメーターを渡すことができる。これにより送信側の参照を自動でメッセージに付け加えることができる。
次のコードがアクターAの中から実行されたら、アクターAのActorRefが自動でメッセージについてくる。
// From within an Actor
greeter ! Greet
Javaでは明示的に送信側のアクターへの参照を付ける。それを行わない場合、代わりにdead-letterアクターへの参照が代わりに使われる。dead-letterアクターは処理されたなかったメッセージが行き着くところで、AkkaのEvent Busでそれらを使える。
The 'sender' reference
送信側への参照は、受信側でメッセージを処理している時に有効になる。各メッセージがユニークな送信側への参照と紐付いているので、「現在の」送信側への参照は新しいメッセージを処理するたびに変わる。なので特定の送信側への参照を持っておきたいならメンバーフィールドで保持するなどするコードを書く必要がある。送信側の参照を得るにはScalaならsender
を使えば良い。
// From within the Greeter Actor
sender ! Greeting(greeting)
Using Inbox
ほとんどのアクターを使ったアプリケーションでは複数のアクターを使用する。アクターモデルの考案者であるCarl Hewittは最近のインタビューでこう言っている。「一つのアクターはアクターじゃない。アクターは複数で役に立つ(適当な訳)」これは重要な見識だ。本当にアクターモデルを活用したいならたくさんのアクターを使うべきだ。アクタープログラミングにおけるどの難しい問題もアクターをもっと増やすことで解決できる。問題をサブタスクにブレイクダウンし、それらの解決を新しいアクターによる処理に移譲するのだ。
しかし、このサンプルでは単純化のために一つのアクターを使う。アクターの中から他のアクターにメッセージを送ることがないので、この一つのアクターにmain
プログラムからメッセージを送る場合、送信側アクターがいないことになってしまう。幸運にもAkkaはこの問題にナイスな解決策を持っている。Inboxだ。
Inboxはactor-in-a-boxを作ることを可能にする。他のアクターにメッセージを送り、応答を受信するための操り人形のアクターをもつことができるような感じだ。Inbox.create
でInboxを生成でき、そのインスタンスのinbox.send
でメッセージを送信できる。内部のアクターは受信したあらゆるメッセージをキューに保存し、inbox.receive
で読み出すことができる。もしキューが空なら、その呼出はメッセージを受信するまでブロックする。超シンプル。
もしかしたら知ってるかもしれないが、ブロッキングはパフォーマンスやスケーラビリティを著しく阻害しうる。なので十分慎重に使用すべきだ。とは言ったものの、このサンプルではそれを使う。サンプルが単純になるし、メッセージのフローを追いやすいから。
ではGreeterアクターを動かすドライバーとなるコードを書いてこのチュートリアルを終えよう。
// Create an "actor-in-a-box"
val inbox = Inbox.create(system)
// Tell the 'greeter' to change its 'greeting' message
greeter tell WhoToGreet("akka")
// Ask the 'greeter for the latest 'greeting'
// Reply should go to the mailbox
inbox.send(greeter, Greet)
// Wait 5 seconds for the reply with the 'greeting' message
val Greeting(message) = inbox.receive(5.seconds)
println(s"Greeting: $message")
Test the App
ScakaTestを使ったユニットテストを用意してある。このテストはAkkaの素晴らしいTestKitモジュールを使っている。TestKitはテストを容易にし、並列コードを検証する。
サンプルのソースコード、テストのソースコードを変更すると、テストは自動で再実行される。
Run the App
おめでとう!
これでこのチュートリアルはほぼ終わりだ。このサンプルではシンプルなAkkaアプリケーションを作った。まだこのサンプルコードの全体を見ていないのなら、今がその時だ。
import akka.actor.{ ActorRef, ActorSystem, Props, Actor, Inbox }
import scala.concurrent.duration._
case object Greet
case class WhoToGreet(who: String)
case class Greeting(message: String)
class Greeter extends Actor {
var greeting = ""
def receive = {
case WhoToGreet(who) => greeting = s"hello, $who"
case Greet => sender ! Greeting(greeting) // Send the current greeting back to the sender
}
}
object HelloAkkaScala extends App {
// Create the 'helloakka' actor system
val system = ActorSystem("helloakka")
// Create the 'greeter' actor
val greeter = system.actorOf(Props[Greeter], "greeter")
// Create an "actor-in-a-box"
val inbox = Inbox.create(system)
// Tell the 'greeter' to change its 'greeting' message
greeter.tell(WhoToGreet("akka"), ActorRef.noSender)
// Ask the 'greeter for the latest 'greeting'
// Reply should go to the "actor-in-a-box"
inbox.send(greeter, Greet)
// Wait 5 seconds for the reply with the 'greeting' message
val Greeting(message1) = inbox.receive(5.seconds)
println(s"Greeting: $message1")
// Change the greeting and ask for it again
greeter.tell(WhoToGreet("typesafe"), ActorRef.noSender)
inbox.send(greeter, Greet)
val Greeting(message2) = inbox.receive(5.seconds)
println(s"Greeting: $message2")
val greetPrinter = system.actorOf(Props[GreetPrinter])
// after zero seconds, send a Greet message every second to the greeter with a sender of the greetPrinter
system.scheduler.schedule(0.seconds, 1.second, greeter, Greet)(system.dispatcher, greetPrinter)
}
// prints a greeting
class GreetPrinter extends Actor {
def receive = {
case Greeting(message) => println(message)
}
}
サンプルを改造してコンパイル、再実行してみよう。
Inspect the App
Inspectビューから動作中のアプリケーションのアクターに何が起こっているかを見ることができる。アクターシステムに含まれるアクターのリストを見ることができる。
各アクターまで掘り下げてみると、たくさんの統計データやアクターの情報が表示されているだろう。Deviationsの欄ではアクターに起きた問題が表示される。
Next Step
Akka Documentationではこのチュートリアルで触れたそれぞれのトピックの奥深くまでカバーしている。
AkkaチームのブログLet It Crashに多くの記事があり良い追加の情報が得られる。
質問があればためらわず、Google Groupのakka-userに投稿しよう。