§Pekko Typedとの統合
Pekko 2.6では、新しい型付きActor API ("Pekko Typed") が安定版としてマークされました。型付きAPIは現在、PekkoのメインAPIとなっています。型付きAPIでは、各アクターは処理できるメッセージタイプを宣言する必要があり、型システムは、このタイプのメッセージのみをアクターに送信できることを強制します。PlayはPekko Typedを完全に採用しているわけではありませんが、Playアプリケーションに統合するためのAPIをいくつか提供しています。
注意: PekkoのクラシックAPIは引き続き完全にサポートされており、既存のアプリケーションは引き続き使用できます。PekkoクラシックAPIを非推奨にしたり削除したりする予定はありません。
§Pekko Actor Typedのスタイル
PekkoのActor Typed APIには、次の2つのスタイルがあります。
- 値を持つアクターの
Behavior
を定義することに基づいた「関数型プログラミング」スタイル。および - サブクラスでアクターの
Behavior
を定義することに基づいた「オブジェクト指向」スタイル。
たとえば、これは挨拶を返すシンプルなアクターの例です
- Scala FP
-
import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.Behavior object HelloActor { final case class SayHello( name: String, replyTo: ActorRef[String], ) def create(): Behavior[SayHello] = { Behaviors.receiveMessage[SayHello] { case SayHello(name, replyTo) => replyTo ! s"Hello, $name" Behaviors.same } } }
- Scala OO
-
import org.apache.pekko.actor.typed.scaladsl.AbstractBehavior import org.apache.pekko.actor.typed.scaladsl.ActorContext import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.Behavior object HelloActor { final case class SayHello( name: String, replyTo: ActorRef[String], ) def create(): Behavior[HelloActor.SayHello] = { Behaviors.setup(context => new HelloActor(context)) } } final class HelloActor private ( context: ActorContext[HelloActor.SayHello], ) extends AbstractBehavior(context) { import HelloActor._ def onMessage(msg: SayHello): HelloActor = { msg.replyTo ! s"Hello, ${msg.name}" this } }
- Java FP
-
import org.apache.pekko.actor.typed.ActorRef; import org.apache.pekko.actor.typed.Behavior; import org.apache.pekko.actor.typed.javadsl.Behaviors; public final class HelloActor { public static final class SayHello { public final String name; public final ActorRef<String> replyTo; public SayHello(String name, ActorRef<String> replyTo) { this.name = name; this.replyTo = replyTo; } } public static Behavior<HelloActor.SayHello> create() { return Behaviors.receiveMessage( (SayHello message) -> { message.replyTo.tell("Hello, " + message.name); return Behaviors.same(); }); } }
- Java OO
-
import org.apache.pekko.actor.typed.ActorRef; import org.apache.pekko.actor.typed.Behavior; import org.apache.pekko.actor.typed.javadsl.AbstractBehavior; import org.apache.pekko.actor.typed.javadsl.ActorContext; import org.apache.pekko.actor.typed.javadsl.Behaviors; import org.apache.pekko.actor.typed.javadsl.Receive; public final class HelloActor extends AbstractBehavior<HelloActor.SayHello> { public static final class SayHello { public final String name; public final ActorRef<String> replyTo; public SayHello(String name, ActorRef<String> replyTo) { this.name = name; this.replyTo = replyTo; } } public static Behavior<HelloActor.SayHello> create() { return Behaviors.setup((ctx) -> new HelloActor(ctx)); } private HelloActor(ActorContext<HelloActor.SayHello> context) { super(context); } @Override public Receive<SayHello> createReceive() { return newReceiveBuilder().onMessage(SayHello.class, this::onHello).build(); } private Behavior<SayHello> onHello(SayHello message) { message.replyTo.tell("Hello, " + message.name); return this; } }
構成値を返すためにPlayのConfiguration
に依存するアクターの例を次に示します。
- Scala FP
-
import com.google.inject.Provides import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.Behavior import play.api.libs.concurrent.ActorModule import play.api.Configuration object ConfiguredActor extends ActorModule { type Message = GetConfig final case class GetConfig(replyTo: ActorRef[String]) @Provides def create(configuration: Configuration): Behavior[GetConfig] = { Behaviors.setup { _ => val config = configuration.get[String]("my.config") Behaviors.receiveMessage[GetConfig] { case GetConfig(replyTo) => replyTo ! config Behaviors.same } } } }
- Scala OO
-
import javax.inject.Inject import org.apache.pekko.actor.typed.scaladsl.AbstractBehavior import org.apache.pekko.actor.typed.scaladsl.ActorContext import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.Behavior import play.api.Configuration object ConfiguredActor { final case class GetConfig(replyTo: ActorRef[String]) def create( configuration: Configuration, ): Behavior[ConfiguredActor.GetConfig] = { Behaviors.setup { context => new ConfiguredActor(context, configuration) } } } final class ConfiguredActor private ( context: ActorContext[ConfiguredActor.GetConfig], configuration: Configuration, ) extends AbstractBehavior(context) { import ConfiguredActor._ val config = configuration.get[String]("my.config") def onMessage(msg: GetConfig): ConfiguredActor = { msg.replyTo ! config this } }
- Java FP
-
import com.typesafe.config.Config; import org.apache.pekko.actor.typed.ActorRef; import org.apache.pekko.actor.typed.Behavior; import org.apache.pekko.actor.typed.javadsl.Behaviors; public final class ConfiguredActor { public static final class GetConfig { public final ActorRef<String> replyTo; public GetConfig(ActorRef<String> replyTo) { this.replyTo = replyTo; } } public static Behavior<ConfiguredActor.GetConfig> create(Config config) { String myConfig = config.getString("my.config"); return Behaviors.receiveMessage( (GetConfig message) -> { message.replyTo.tell(myConfig); return Behaviors.same(); }); } }
- Java OO
-
import com.typesafe.config.Config; import org.apache.pekko.actor.typed.ActorRef; import org.apache.pekko.actor.typed.Behavior; import org.apache.pekko.actor.typed.javadsl.AbstractBehavior; import org.apache.pekko.actor.typed.javadsl.ActorContext; import org.apache.pekko.actor.typed.javadsl.Behaviors; import org.apache.pekko.actor.typed.javadsl.Receive; public final class ConfiguredActor extends AbstractBehavior<ConfiguredActor.GetConfig> { public static final class GetConfig { public final ActorRef<String> replyTo; public GetConfig(ActorRef<String> replyTo) { this.replyTo = replyTo; } } private final String config; public static Behavior<ConfiguredActor.GetConfig> create(Config config) { return Behaviors.setup((ctx) -> new ConfiguredActor(ctx, config)); } private ConfiguredActor(ActorContext<ConfiguredActor.GetConfig> context, Config config) { super(context); this.config = config.getString("my.config"); } @Override public Receive<GetConfig> createReceive() { return newReceiveBuilder().onMessage(GetConfig.class, this::onGetConfig).build(); } private Behavior<GetConfig> onGetConfig(GetConfig message) { message.replyTo.tell(config); return this; } }
§依存性注入
アクターの動作に可変状態がある場合(オブジェクト指向スタイルで時々発生します)、複数のActorRef
で同じBehavior
インスタンスを共有しないようにしてください。問題を回避する一般的な方法を次に示します。
- 可変状態のない設計を検討してください。
- たとえば、
ActorRef
のみをバインドするなど、ActorRef
インスタンスのみを公開することで、Behavior
インスタンスをリークしないでください。 - 目的がアクターの単一インスタンスのみを持つことである場合は、
@Singleton
または.asEagerSingleton
を使用するなど、Behavior
とActorRef
の両方がシングルトンであることを確認してください。 - 代わりに、同じアクターの複数のインスタンスが存在する必要がある場合は、Guiceで
@Named
または.annotatedWith(Names.named(..))
を使用するなど、Behavior
とActorRef
の両方が名前付きシングルトンであることを確認してください。
§コンパイル時の依存性注入
Pekko Actor Typedのコンパイル時の依存性注入を使用するには、アクターのBehavior
値を作成し、それを使用してアクターを生成する必要があります。
- Scala
-
import org.apache.pekko.actor.typed.scaladsl.adapter._ import play.api._ import play.api.routing.Router final class AppComponents(context: ApplicationLoader.Context) extends BuiltInComponentsFromContext(context) with NoHttpFiltersComponents { val router = Router.empty val helloActor = { actorSystem.spawn(HelloActor.create(), "hello-actor") } val configuredActor = { val behavior = ConfiguredActor.create(configuration) actorSystem.spawn(behavior, "configured-actor") } val main = new Main(helloActor, configuredActor) }
- Java
-
import java.util.Collections; import java.util.List; import org.apache.pekko.actor.typed.ActorRef; import org.apache.pekko.actor.typed.javadsl.Adapter; import play.ApplicationLoader; import play.BuiltInComponentsFromContext; import play.mvc.EssentialFilter; import play.routing.Router; public final class AppComponents extends BuiltInComponentsFromContext { public final ActorRef<HelloActor.SayHello> helloActor; public final ActorRef<ConfiguredActor.GetConfig> configuredActor; public final Main main; public AppComponents(ApplicationLoader.Context context) { super(context); helloActor = Adapter.spawn(actorSystem(), HelloActor.create(), "hello-actor"); configuredActor = Adapter.spawn(actorSystem(), ConfiguredActor.create(config()), "configured-actor"); main = new Main(helloActor, configuredActor); } @Override public Router router() { return Router.empty(); } @Override public List<EssentialFilter> httpFilters() { return Collections.emptyList(); } }
§実行時の依存性注入
実行時の依存性注入の場合、関数型プログラミングスタイルを使用している場合は、PekkoGuiceSupport
の「typed」メソッドを使用します。オブジェクト指向スタイルの場合は、ActorRef
のProvider
を作成してバインドする必要があります。
たとえば、アプリケーションまたはシステムでインジェクトする必要があるコンポーネントが次のように指定されている場合
- Scala
-
import javax.inject.Inject import javax.inject.Singleton import org.apache.pekko.actor.typed.ActorRef @Singleton final class Main @Inject() ( val helloActor: ActorRef[HelloActor.SayHello], val configuredActor: ActorRef[ConfiguredActor.GetConfig], )
- Java
-
import javax.inject.Inject; import javax.inject.Singleton; import org.apache.pekko.actor.typed.ActorRef; @Singleton public final class Main { public final ActorRef<HelloActor.SayHello> helloActor; public final ActorRef<ConfiguredActor.GetConfig> configuredActor; @Inject public Main( ActorRef<HelloActor.SayHello> helloActor, ActorRef<ConfiguredActor.GetConfig> configuredActor) { this.helloActor = helloActor; this.configuredActor = configuredActor; } }
GuiceのModule
を次のように定義できます
- Scala FP
-
import com.google.inject.AbstractModule import play.api.libs.concurrent.PekkoGuiceSupport object AppModule extends AbstractModule with PekkoGuiceSupport { override def configure() = { bindTypedActor(HelloActor.create(), "hello-actor") // uses "create" method bindTypedActor(ConfiguredActor, "configured-actor") // uses the object itself } }
- Scala OO
-
import javax.inject.Inject import com.google.inject.AbstractModule import com.google.inject.Provider import com.google.inject.TypeLiteral import org.apache.pekko.actor.typed.scaladsl.adapter._ import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.ActorSystem import play.api.libs.concurrent.PekkoGuiceSupport import play.api.Configuration object AppModule extends AbstractModule with PekkoGuiceSupport { override def configure() = { bindTypedActor(HelloActor.create(), "hello-actor") bind(new TypeLiteral[ActorRef[ConfiguredActor.GetConfig]]() {}) .toProvider(classOf[ConfiguredActorProvider]) .asEagerSingleton() } private class ConfiguredActorProvider @Inject() ( actorSystem: ActorSystem, configuration: Configuration, ) extends Provider[ActorRef[ConfiguredActor.GetConfig]] { def get() = { val behavior = ConfiguredActor.create(configuration) actorSystem.spawn(behavior, "configured-actor") } } }
- Java FP
-
import com.google.inject.AbstractModule; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.typesafe.config.Config; import javax.inject.Inject; import org.apache.pekko.actor.ActorSystem; import org.apache.pekko.actor.typed.ActorRef; import org.apache.pekko.actor.typed.javadsl.Adapter; public class AppModule extends AbstractModule { @Override protected void configure() { bind(new TypeLiteral<ActorRef<HelloActor.SayHello>>() {}) .toProvider(HelloActorProvider.class) .asEagerSingleton(); bind(new TypeLiteral<ActorRef<ConfiguredActor.GetConfig>>() {}) .toProvider(ConfiguredActorProvider.class) .asEagerSingleton(); } public static class HelloActorProvider implements Provider<ActorRef<HelloActor.SayHello>> { private final ActorSystem actorSystem; @Inject public HelloActorProvider(ActorSystem actorSystem) { this.actorSystem = actorSystem; } @Override public ActorRef<HelloActor.SayHello> get() { return Adapter.spawn(actorSystem, HelloActor.create(), "hello-actor"); } } public static class ConfiguredActorProvider implements Provider<ActorRef<ConfiguredActor.GetConfig>> { private final ActorSystem actorSystem; private final Config config; @Inject public ConfiguredActorProvider(ActorSystem actorSystem, Config config) { this.actorSystem = actorSystem; this.config = config; } @Override public ActorRef<ConfiguredActor.GetConfig> get() { return Adapter.spawn(actorSystem, ConfiguredActor.create(config), "configured-actor"); } } }
- Java OO
-
import com.google.inject.AbstractModule; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.typesafe.config.Config; import javax.inject.Inject; import org.apache.pekko.actor.ActorSystem; import org.apache.pekko.actor.typed.ActorRef; import org.apache.pekko.actor.typed.javadsl.Adapter; public class AppModule extends AbstractModule { @Override protected void configure() { bind(new TypeLiteral<ActorRef<HelloActor.SayHello>>() {}) .toProvider(HelloActorProvider.class) .asEagerSingleton(); bind(new TypeLiteral<ActorRef<ConfiguredActor.GetConfig>>() {}) .toProvider(ConfiguredActorProvider.class) .asEagerSingleton(); } public static class HelloActorProvider implements Provider<ActorRef<HelloActor.SayHello>> { private final ActorSystem actorSystem; @Inject public HelloActorProvider(ActorSystem actorSystem) { this.actorSystem = actorSystem; } @Override public ActorRef<HelloActor.SayHello> get() { return Adapter.spawn(actorSystem, HelloActor.create(), "hello-actor"); } } public static class ConfiguredActorProvider implements Provider<ActorRef<ConfiguredActor.GetConfig>> { private final ActorSystem actorSystem; private final Config config; @Inject public ConfiguredActorProvider(ActorSystem actorSystem, Config config) { this.actorSystem = actorSystem; this.config = config; } @Override public ActorRef<ConfiguredActor.GetConfig> get() { return Adapter.spawn(actorSystem, ConfiguredActor.create(config), "configured-actor"); } } }
§AskPattern
と型付きスケジューラーの使用
たとえば、Controller
からなどの別のアクターの外部からアクターと対話する場合、AskPattern.ask
を使用してアクターにメッセージを送信し、応答を取得する必要があります。AskPattern.ask
メソッドには、依存性注入で取得できるpekko.actor.typed.Scheduler
が必要です。
§実行時の依存性注入
実行時の依存性注入は、Playの他の実行時DIモジュールと同様に機能します。Scheduler
はデフォルトのバインディングの一部であるため、モジュールは自動的に有効になり、インスタンスをインジェクトに使用できます。
§コンパイル時の依存性注入
コンパイル時DIを使用している場合は、次のようにコンポーネントを使用することで、Scheduler
にアクセスできます。
- Java
-
import play.ApplicationLoader; import play.BuiltInComponentsFromContext; import play.components.PekkoTypedComponents; import play.controllers.AssetsComponents; import play.filters.components.HttpFiltersComponents; import play.routing.Router; public class ComponentsWithTypedScheduler extends BuiltInComponentsFromContext implements PekkoTypedComponents, AssetsComponents, HttpFiltersComponents { public ComponentsWithTypedScheduler(ApplicationLoader.Context context) { super(context); } @Override public Router router() { return Router.empty(); } }
- Scala
-
import play.api.libs.concurrent.PekkoTypedComponents import play.api.routing.Router import play.api.Application import play.api.ApplicationLoader import play.api.ApplicationLoader.Context import play.api.BuiltInComponentsFromContext import play.filters.HttpFiltersComponents class MyApplicationLoaderUsingTypedScheduler extends ApplicationLoader { override def load(context: Context): Application = { new ComponentsWithTypedScheduler(context).application } } class ComponentsWithTypedScheduler(context: Context) extends BuiltInComponentsFromContext(context) with HttpFiltersComponents with PekkoTypedComponents { override lazy val router: Router = Router.empty }
このドキュメントにエラーを見つけましたか?このページのソースコードはこちらにあります。ドキュメントのガイドラインを読んだ後、プルリクエストを自由に提出してください。質問やアドバイスを共有したいですか?コミュニティフォーラムにアクセスして、コミュニティとの会話を始めましょう。