Documentation

§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 の慣例に従っています。

§アクターの作成と使用

アクターの作成や使用には、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)
  }
}

いくつかの注意事項があります。

§アクターの依存性注入

必要に応じて、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 は、インスタンス化の際に ConfiguredChildActorConfiguration のインスタンスを提供します。

§設定

デフォルトのアクターシステムの設定は、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()
}

§独自のアクターシステムの使用

クラスローダー、ライフサイクルのフックなどをすべて正しくセットアップするので、組み込みのアクターシステムの使用をおすすめします。独自のアクターシステムを使用することは全然構いません。ただし、次のことを確実に行うことが重要です。

Next: 国際化


このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。