§Pekkoとの統合
Pekkoは、アクターモデルを使用して抽象化レベルを高め、正しく並列でスケーラブルなアプリケーションを構築するためのより良いプラットフォームを提供します。フォールトトレランスのために、「Let it crash」モデルを採用しています。これは、自己修復するアプリケーション、つまり停止することのないシステムを構築するために、通信業界で大きな成功を収めて使用されてきました。アクターはまた、透過的な分散のための抽象化と、真にスケーラブルでフォールトトレラントなアプリケーションの基礎を提供します。
§アプリケーションアクターシステム
Pekkoは、アクターシステムと呼ばれる複数のコンテナで動作できます。アクターシステムは、含まれているアクターを実行するために構成されたリソースを管理します。
Playアプリケーションは、アプリケーションで使用される特別なアクターシステムを定義します。このアクターシステムは、アプリケーションのライフサイクルに従い、アプリケーションの再起動時に自動的に再起動します。
§アクターの作成
Pekkoの使用を開始するには、アクターを作成する必要があります。以下は、要求に応じて挨拶するだけの単純なアクターです。
import org.apache.pekko.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
}
}
このアクターは、いくつかのPekkoの規則に従います
- 送受信するメッセージ、つまり*プロトコル*は、コンパニオンオブジェクトで定義されます
- また、コンパニオンオブジェクトに`props`メソッドを定義し、それを作成するための`props`を返します
§アクターの作成と使用
アクターを作成および/または使用するには、`ActorSystem`が必要です。これは、次のようにActorSystemへの依存関係を宣言することで取得できます
import javax.inject._
import actors.HelloActor
import org.apache.pekko.actor._
import play.api.mvc._
@Singleton
class Application @Inject() (system: ActorSystem, cc: ControllerComponents) extends AbstractController(cc) {
val helloActor = system.actorOf(HelloActor.props, "hello-actor")
// ...
}
`actorOf`メソッドは、新しいアクターを作成するために使用されます。このコントローラーをシングルトンとして宣言していることに注意してください。これは、アクターを作成してその参照を格納しているためです。コントローラーがシングルトンとしてスコープされていない場合、これはコントローラーが作成されるたびに新しいアクターが作成されることを意味し、最終的には同じシステムに同じ名前のアクターを2つ持つことができないため、例外がスローされます。
§アクターに要求する
アクターでできる最も基本的なことは、メッセージを送信することです。アクターにメッセージを送信しても、応答はありません。これは*tell*パターンとしても知られています。
ただし、Webアプリケーションでは、HTTPはリクエストとレスポンスを持つプロトコルであるため、*tell*パターンは役に立たないことがよくあります。この場合、*ask*パターンを使用する可能性が高くなります。 *ask*パターンは`Future`を返し、それを独自の result 型にマップできます。
以下は、*ask*パターンで`HelloActor`を使用する例です
import scala.concurrent.duration._
import org.apache.pekko.pattern.ask
implicit val timeout: Timeout = 5.seconds
def sayHello(name: String) = Action.async {
(helloActor ? SayHello(name)).mapTo[String].map { message => Ok(message) }
}
いくつかの注意点
- *ask*パターンをインポートする必要があり、これによりアクターに`?`演算子が提供されます。
- *ask*の戻り値の型は`Future[Any]`です。通常、アクターに*ask*した後に最初に行うことは、`mapTo`メソッドを使用して、それを期待する型にマップすることです。
- 暗黙のタイムアウトがスコープ内に必要です-*ask*パターンにはタイムアウトが必要です。アクターが応答するのにそれ以上の時間がかかると、返された future はタイムアウトエラーで完了します。
§アクターの依存性注入
必要に応じて、Guiceにアクターをインスタンス化させ、コントローラーとコンポーネントが依存するようにアクター参照をバインドさせることができます。
たとえば、Play設定に依存するアクターを作成する場合、次のようにします
import javax.inject._
import org.apache.pekko.actor._
import play.api.Configuration
object ConfiguredActor {
case object GetConfig
}
class ConfiguredActor @Inject() (configuration: Configuration) extends Actor {
import ConfiguredActor._
val config = configuration.getOptional[String]("my.config").getOrElse("none")
def receive = {
case GetConfig =>
sender() ! config
}
}
Playは、アクターのバインディングを提供するのに役立つヘルパーを提供します。これにより、アクター自体に依存性注入を行うことができ、アクターのアクター参照を他のコンポーネントに注入できます。これらのヘルパーを使用してアクターをバインドするには、依存性注入のドキュメントで説明されているようにモジュールを作成し、`PekkoGuiceSupport`トレイトを mixin し、`bindActor`メソッドを使用してアクターをバインドします
import actors.ConfiguredActor
import com.google.inject.AbstractModule
import play.api.libs.concurrent.PekkoGuiceSupport
class MyModule extends AbstractModule with PekkoGuiceSupport {
override def configure = {
bindActor[ConfiguredActor]("configured-actor")
}
}
このアクターは、`configured-actor`という名前になり、注入のために`configured-actor`名で修飾されます。これで、コントローラーや他のコンポーネントでアクターに依存できるようになりました
import javax.inject._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext
import actors.ConfiguredActor._
import org.apache.pekko.actor._
import org.apache.pekko.pattern.ask
import org.apache.pekko.util.Timeout
import play.api.mvc._
@Singleton
class Application @Inject() (
@Named("configured-actor") configuredActor: ActorRef,
components: ControllerComponents
)(
implicit ec: ExecutionContext
) extends AbstractController(components) {
implicit val timeout: Timeout = 5.seconds
def getConfig = Action.async {
(configuredActor ? GetConfig).mapTo[String].map { message => Ok(message) }
}
}
§子アクターの依存性注入
上記はルートアクターを注入するのに適していますが、作成するアクターの多くは、Playアプリのライフサイクルにバインドされていない子アクターであり、追加の状態が渡される場合があります。
子アクターの依存性注入を支援するために、PlayはGuiceのAssistedInjectサポートを利用しています。
注入される設定とキーに依存する、次のようなアクターがあるとします
import javax.inject._
import com.google.inject.assistedinject.Assisted
import org.apache.pekko.actor._
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.getOptional[String](key).getOrElse("none")
def receive = {
case GetConfig =>
sender() ! config
}
}
`key`パラメーターが`@Assisted`として宣言されていることに注意してください。これは、手動で提供されることを示しています。
`Factory`トレイトも定義しました。これは`key`を受け取り、`Actor`を返します。これは実装しません。Guiceが実装を提供します。実装は、`key`パラメーターを渡すだけでなく、`Configuration`依存関係を見つけて注入します。トレイトは`Actor`を返すだけなので、このアクターをテストするときは、任意のアクターを返すファクトリーを注入できます。たとえば、これにより、実際のアクターではなく、モックされた子アクターを注入できます。
これで、これに依存するアクターは`InjectedActorSupport`を拡張でき、作成したファクトリーに依存できます
import javax.inject._
import org.apache.pekko.actor._
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`を使用して子アクターを作成し、その参照を取得して、キーを渡します。 2番目のパラメーター(この例では`key`)は、子アクターの名前として使用されます。
最後に、アクターをバインドする必要があります。モジュールでは、`bindActorFactory`メソッドを使用して親アクターをバインドし、子ファクトリーを子実装にバインドします
import actors._
import com.google.inject.AbstractModule
import play.api.libs.concurrent.PekkoGuiceSupport
class MyModule extends AbstractModule with PekkoGuiceSupport {
override def configure = {
bindActor[ParentActor]("parent-actor")
bindActorFactory[ConfiguredChildActor, ConfiguredChildActor.Factory]
}
}
これにより、Guiceは`ConfiguredChildActor.Factory`のインスタンスを自動的にバインドします。これは、インスタンス化されるときに`Configuration`のインスタンスを`ConfiguredChildActor`に提供します。
§設定
デフォルトのアクターシステム設定は、Playアプリケーション設定ファイルから読み取られます。たとえば、アプリケーションアクターシステムのデフォルトのディスパッチャーを設定するには、`conf/application.conf`ファイルに次の行を追加します
pekko.actor.default-dispatcher.fork-join-executor.parallelism-max = 64
pekko.actor.debug.receive = on
Pekkoロギング設定については、ロギングの設定を参照してください。
§組み込みアクターシステム名
デフォルトでは、Playアクターシステムの名前は`application`です。これは、`conf/application.conf`のエントリを介して変更できます
play.pekko.actor-system = "custom-name"
**注:**この機能は、PlayアプリケーションActorSystemをPekkoクラスターに配置する場合に役立ちます。
§独自のアクターシステムの使用
組み込みのアクターシステムを使用することをお勧めしますが、正しいクラスローダー、ライフサイクルフックなどを設定するため、独自のアクターシステムを使用することを妨げるものは何もありません。ただし、次のことを確認することが重要です
- Playのシャットダウン時にアクターシステムをシャットダウンするために、停止フックを登録します
- Play 環境から正しいクラスローダーを渡します。そうでない場合、Pekkoはアプリケーションクラスを見つけることができません
- Pekko設定をデフォルトの`pekko`設定から読み取らないようにしてください。これはPlayのアクターシステムですでに使用されているため、システムが同じリモートポートにバインドしようとした場合などに問題が発生します
§Pekko協調シャットダウン
Playは、Pekkoの調整されたシャットダウンを使用して、Application
とServer
のシャットダウンを処理します。詳細は、共通セクションの調整されたシャットダウンを参照してください。
注意: Playは、内部のActorSystem
のシャットダウンのみを処理します。追加のActorシステムを使用している場合は、すべて終了していることを確認し、終了コードを調整されたシャットダウンに移行してください。
§Pekkoクラスタ
Playアプリケーションを既存のPekkoクラスタに参加させることができます。その場合は、クラスタから正常に離脱することをお勧めします。Playには、正常な離脱を処理するPekkoの調整されたシャットダウンが付属しています。
既にカスタムのクラスタ離脱コードがある場合は、Pekkoの処理に置き換えることをお勧めします。詳細は、Pekkoのドキュメントを参照してください。
§Pekkoバージョンの更新
Playではまだ使用されていない、より新しいバージョンのPekkoを使用する場合は、build.sbt
ファイルに以下を追加できます。
// The newer Pekko version you want to use.
val pekkoVersion = "1.0.0"
// Pekko dependencies used by Play
libraryDependencies ++= Seq(
"org.apache.pekko" %% "pekko-actor" % pekkoVersion,
"org.apache.pekko" %% "pekko-actor-typed" % pekkoVersion,
"org.apache.pekko" %% "pekko-stream" % pekkoVersion,
"org.apache.pekko" %% "pekko-slf4j" % pekkoVersion,
"org.apache.pekko" %% "pekko-serialization-jackson" % pekkoVersion,
// Only if you are using Pekko Testkit
"org.apache.pekko" %% "pekko-testkit" % pekkoVersion
)
もちろん、他のPekkoアーティファクトを推移的に追加することもできます。sbt-dependency-graphを使用して、ビルドをより詳細に検査し、明示的に追加する必要があるアーティファクトを確認してください。
Nettyサーバーバックエンドに切り替えておらず、PlayのデフォルトのPekko HTTPサーバーバックエンドを使用している場合は、Pekko HTTPも更新する必要があります。そのため、その依存関係も明示的に追加する必要があります。
// The newer Pekko HTTP version you want to use.
val pekkoHTTPVersion = "1.0.0"
// Pekko HTTP dependency used by Play
libraryDependencies ++= Seq(
"org.apache.pekko" %% "pekko-http-core" % pekkoHTTPVersion
)
注意: このような更新を行う場合は、Pekkoのバイナリ互換性ルールに従う必要があります。また、他のPekkoアーティファクトを手動で追加する場合は、バージョン混在は許可されていないため、すべてのPekkoアーティファクトのバージョンを一致させる必要があります。
注意: 依存関係を解決するとき、sbtはこのプロジェクトで宣言されているか、推移的に追加されている最新のものを取得します。つまり、Playが宣言されているものよりも新しいPekko(またはPekko HTTP)バージョンに依存している場合、Playのバージョンが優先されます。sbtがどのように削除を行うかについての詳細はこちらを参照してください。
次: メッセージによる国際化
このドキュメントにエラーが見つかりましたか?このページのソースコードはこちらにあります。ドキュメントガイドラインをお読みになった後、プルリクエストを送信してください。質問やアドバイスがあれば、コミュニティフォーラムにアクセスして、コミュニティとの会話を始めてください。