§Akka の統合
Akka は抽象レベルを上げるためにアクターモデルを利用し、正しい平行処理のスケーラブルなアプリケーションを構築するためにより良いプラットフォームを提供します。耐障害性を確保するために、通信業界において、自己回復することで決して停止することがないアプリケーションの構築で大きな成功をおさめている ‘Let it crash’ という設計モデルを採用しています。また、アクターモデルは透過的な分散環境や本当にスケーラブルで耐障害性の高いアプリケーションのための抽象化も提供します。
§アプリケーションのアクターシステム
Akka は アクターシステムと呼ばれるいくつかのコンテナを持ちます。それぞれのアクターシステムは、それに含まれるアクターを動かすためのリソースを管理します。
Play アプリケーションには、アプリケーション自身が使う特別なアクターシステムが定義されています。このアクターシステムはアプリケーションのライフサイクルに追従し、アプリケーションと共に自動的に再起動します。
§アクターの記述
Akka を使い始めるには、アクターを記述する必要があります。以下の例は、どんな問い合わせに対しても単純に Hello を返すだけのシンプルなアクターです。
import akka.actor._
object HelloActor {
def props = Props[HelloActor]
case class SayHello(name: String)
}
class HelloActor extends Actor {
import HelloActor._
def receive = {
case SayHello(name: String) =>
sender() ! "Hello, " + name
}
}
このアクターは、いくつかの Akka の慣例に従っています。
- アクターが送受信するメッセージや 手順 は、コンパニオンオブジェクトに定義されています。
- 同様に、アクターを作成するためのプロパティを返す
props
メソッドをコンパニオンオブジェクトに定義しています。
§アクターの作成と使用
アクターの作成や使用には、ActorSystem
が必要です。この ActorSystem は次のように依存関係を宣言することで取得できます。
import play.api.mvc._
import akka.actor._
import javax.inject._
import actors.HelloActor
@Singleton
class Application @Inject() (system: ActorSystem) extends Controller {
val helloActor = system.actorOf(HelloActor.props, "hello-actor")
//...
}
actorOf
メソッドは新規のアクターを作成するために使われます。このコントローラーはシングルトンとして宣言することに注意して下さい。これはアクターの作成や参照の保存のために必要で、仮に、このコントローラーがシングルトンの適用範囲外の場合、新しいアクターはコントローラーが作成される都度作成されることになり、同一システム内で同一名称のアクターを 2 つは持てないため、最終的に例外をスローすることになります。
§アクターについて
アクターを使ってできる最も基本的なことは、アクターにメッセージを送信することです。メッセージをアクターに送信しても応答は得られず、送信しっ放しとなります。これは tell パターンとしても知られています。
しかし、web アプリケーションにおいては、HTTP がリクエストとレスポンスを持つプロトコルであるため、この tell パターンはあまり有用ではありません。この場合、 ask パターンが使われる傾向にあるようです。ask パターンは、結果の型に関連付けられる Future
を返します。
以下は、ask パターンを用いた HelloActor
の使用例です。
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
import akka.pattern.ask
implicit val timeout = 5.seconds
def sayHello(name: String) = Action.async {
(helloActor ? SayHello(name)).mapTo[String].map { message =>
Ok(message)
}
}
いくつかの注意事項があります。
- ask パターンはインポートされる必要があり、そしてアクター上で
?
演算子を提供します。 - ask の戻り値の型は
Future[Any]
で、通常、アクターに要求した後に最初にまずやりたいことは、期待する型への対応付けをmapTo
メソッドを使用して行うことです。 - 暗黙のタイムアウトは適用範囲内において必要で、ask パターンはタイムアウトを持つべきです。アクターが応答にタイムアウトよりも長い時間を掛けた場合は、返された future はタイムアウトエラーで完了します。
§アクターの依存性注入
必要に応じて、Guice を用いてアクターのインスタンスを生成し、コントローラーとコンポーネントを依存させるためにアクターの参照をバインドすることができます。
例えば、Play の設定に依存したアクターが必要な場合、このようにします。
import akka.actor._
import javax.inject._
import play.api.Configuration
object ConfiguredActor {
case object GetConfig
}
class ConfiguredActor @Inject() (configuration: Configuration) extends Actor {
import ConfiguredActor._
val config = configuration.getString("my.config").getOrElse("none")
def receive = {
case GetConfig =>
sender() ! config
}
}
Play は、アクターのバインドに役立つ、いくつかのヘルパーを提供します。それらはアクター自身が依存性注入されることを許可し、そのアクターに対するアクター参照が他のコンポーネントに注入されることも許可します。これらのヘルパーを用いたアクターをバインドするには、依存性注入ドキュメント の説明に従ってモジュールを作成し、AkkaGuiceSupport
トレイトをミックスインし、bindActor
メソッドを使用してアクターをバインドします。
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
import actors.ConfiguredActor
class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[ConfiguredActor]("configured-actor")
}
}
このアクターは configured-actor
という名前になり、また configured-actor
という名前で注入を限定します。これにより、コントローラーや他のコンポーネントでこのアクターに依存することができます。
import play.api.mvc._
import akka.actor._
import akka.pattern.ask
import akka.util.Timeout
import javax.inject._
import actors.ConfiguredActor._
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
@Singleton
class Application @Inject() (@Named("configured-actor") configuredActor: ActorRef)
(implicit ec: ExecutionContext) extends Controller {
implicit val timeout: Timeout = 5.seconds
def getConfig = Action.async {
(configuredActor ? GetConfig).mapTo[String].map { message =>
Ok(message)
}
}
}
§子アクターの依存性注入
上記はルートアクターの注入については適していますが、作成するアクターの多くは、Play アプリのライフサイクルにバインドされない子アクターであり、追加の状態が渡される可能性があります。
子アクターの依存性注入を援助するために、Play は、Guice の AssistedInject のサポートを利用しています。
注入される設定に依存する、次のようなアクターと、キーがあるとします。
import akka.actor._
import javax.inject._
import com.google.inject.assistedinject.Assisted
import play.api.Configuration
object ConfiguredChildActor {
case object GetConfig
trait Factory {
def apply(key: String): Actor
}
}
class ConfiguredChildActor @Inject() (configuration: Configuration,
@Assisted key: String) extends Actor {
import ConfiguredChildActor._
val config = configuration.getString(key).getOrElse("none")
def receive = {
case GetConfig =>
sender() ! config
}
}
key
パラメータは @Assisted
として宣言されており、これは手動で供給されることを表していることに注意してください。
また、key
を持ち Actor
を返す Factory
トレイトを定義しました。このトレイトの実装は我々ではなく、Guice が行なってくれ、引数の key
を渡すだけでなく、Configuration
への依存性の注入も行ないます。トレイトは単に Actor
を返すだけなので、このアクターのテストを行った時、任意のアクターを返すファクトリーを注入することができます。例えば、実際の子アクターの代わりにモック化された子アクターを注入することができます。
これに依存するアクターは InjectedActorSupport
を継承することができ、作成したファクトリーに依存することができます。
import akka.actor._
import javax.inject._
import play.api.libs.concurrent.InjectedActorSupport
object ParentActor {
case class GetChild(key: String)
}
class ParentActor @Inject() (
childFactory: ConfiguredChildActor.Factory
) extends Actor with InjectedActorSupport {
import ParentActor._
def receive = {
case GetChild(key: String) =>
val child: ActorRef = injectedChild(childFactory(key), key)
sender() ! child
}
}
子アクターの参照を作成、取得するために injectedChild
を使い、キーを渡します。
最後に、アクターをバインドする必要があります。モジュールの中で bindActorFactory
メソッドを使って親アクターをバインドし、子の実装のために子ファクトリーもバインドします。
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
import actors._
class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[ParentActor]("parent-actor")
bindActorFactory[ConfiguredChildActor, ConfiguredChildActor.Factory]
}
}
これで Guice に ConfiguredChildActor.Factory
のインスタンスを自動的にバインドさせます。ConfiguredChildActor.Factory
は、インスタンス化の際に ConfiguredChildActor
に Configuration
のインスタンスを提供します。
§設定
デフォルトのアクターシステムの設定は、Play アプリケーションの設定ファイルから読み込まれます。例えば、アプリケーションのアクターシステムのデフォルトディスパッチャを変更したい場合は、 conf/application.conf
ファイルにその設定を数行追加します。
akka.actor.default-dispatcher.fork-join-executor.parallelism-max = 64
akka.actor.debug.receive = on
Akka のロギングの設定については、ロギング設定 を参照して下さい。
§設定接頭辞の変更
別の Akka アクターシステムに akka.*
設定を使用したい場合は、別の場所から Akka 設定をロードすることを Play に指示することができます。
play.akka.config = "my-akka"
これにより、akka
接頭辞の代わりに my-akka
接頭辞から設定が読み込まれます。
my-akka.actor.default-dispatcher.fork-join-executor.pool-size-max = 64
my-akka.actor.debug.receive = on
組み込みアクターシステム名
Play のアクターシステム名はデフォルトでは application
となっています。conf/application.conf
の設定で変更することができます。
play.akka.actor-system = "custom-name"
メモ: この機能は Play アプリケーションのアクターシステムを Akka クラスターに配置したい場合に便利です。
§非同期タスクのスケジューリング
Akka では、アクターへのメッセージ送信やタスク (関数または Runnable
) の実行を予約することができます。予約を行うと、結果として Cancellable
のインスタンスが返ってきます。その cancel
メソッドを呼び出すことで、予約した操作の実行をキャンセルすることができます。
例えば、testActor
というアクターに 300 マイクロ秒毎にメッセージを送信するにはこのようにします。
import scala.concurrent.duration._
val cancellable = system.scheduler.schedule(
0.microseconds, 300.microseconds, testActor, "tick")
メモ: この例では
scala.concurrent.duration
に定義されている implicit conversion を利用して、数値を時間単位の異なるDuration
オブジェクトへ変換しています。
同様に、コードブロックを今から 10 ミリ秒後に実行するには、次のように書きます。
import play.api.libs.concurrent.Execution.Implicits.defaultContext
system.scheduler.scheduleOnce(10.milliseconds) {
file.delete()
}
§独自のアクターシステムの使用
クラスローダー、ライフサイクルのフックなどをすべて正しくセットアップするので、組み込みのアクターシステムの使用をおすすめします。独自のアクターシステムを使用することは全然構いません。ただし、次のことを確実に行うことが重要です。
- Play が停止した時にアクターシステムを停止させるための 停止フック を登録する。
- Play 環境 から正しいクラスローダーを渡す。そうでないと、Akka はアプリケーションクラスを見つけることができません。
- Play が Akka の設定を読み込む場所を
play.akka.config
から変更するか、デフォルトのakka
設定から akka の設定を読み込まないようにする。これは、システムが同一のリモートポートにバインドしようとする時のような問題を引き起こすからです。
Next: 国際化
このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。