§Pekkoとの統合
Pekko は、アクターモデルを使用して抽象化レベルを高め、正しく並列処理を行い、スケーラブルなアプリケーションを構築するためのより良いプラットフォームを提供します。フォールトトレランスのために、「Let it crash」モデルを採用しています。これは、自己修復するアプリケーション、つまり停止することのないシステムを構築するために、通信業界で大きな成功を収めて使用されてきました。アクターはまた、透過的な分散のための抽象化と、真にスケーラブルでフォールトトレラントなアプリケーションの基盤を提供します。
§アプリケーションアクターシステム
Pekkoは、アクターシステムと呼ばれる複数のコンテナで動作できます。アクターシステムは、それが含むアクターを実行するために構成されたリソースを管理します。
Playアプリケーションは、アプリケーションで使用される特別なアクターシステムを定義します。このアクターシステムは、アプリケーションのライフサイクルに従い、アプリケーションの再起動時に自動的に再起動します。
§アクターの作成
Pekkoの使用を開始するには、アクターを作成する必要があります。以下は、要求に応じて単に挨拶するだけの簡単なアクターです。
package actors;
import org.apache.pekko.actor.*;
import org.apache.pekko.japi.*;
import actors.HelloActorProtocol.*;
public class HelloActor extends AbstractActor {
public static Props getProps() {
return Props.create(HelloActor.class);
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
SayHello.class,
hello -> {
String reply = "Hello, " + hello.name;
sender().tell(reply, self());
})
.build();
}
}
ここで、`HelloActor` は `getProps` と呼ばれる静的メソッドを定義していることに注意してください。このメソッドは、アクターの作成方法を記述する `Props` オブジェクトを返します。これは、アクターを作成するコードからインスタンス化ロジックを分離するための、Pekkoの優れた慣習です。
ここで示されているもう1つのベストプラクティスは、`HelloActor` が送受信するメッセージが、`HelloActorProtocol` と呼ばれる別のクラスの静的内部クラスとして定義されていることです。
package actors;
public class HelloActorProtocol {
public static class SayHello {
public final String name;
public SayHello(String name) {
this.name = name;
}
}
}
§アクターの作成と使用
アクターを作成および/または使用するには、`ActorSystem` が必要です。これは、`ActorSystem` への依存関係を宣言することで取得できます。その後、`actorOf` メソッドを使用して新しいアクターを作成できます。
アクターでできる最も基本的なことは、メッセージを送信することです。アクターにメッセージを送信しても応答はありません。これは、いわゆる *tell* パターンです。
ただし、Webアプリケーションでは、HTTPはリクエストとレスポンスを持つプロトコルであるため、*tell* パタ-ンは役に立たないことがよくあります。この場合、*ask* パターンを使用する可能性が高くなります。 *ask* パターンは、Scalaの `Future` を返します。これを `scala.compat.java8.FutureConverts.toJava` を使用してJavaの `CompletionStage` に変換し、独自の結果タイプにマップできます。
以下は、*ask* パターンで `HelloActor` を使用した例です。
import org.apache.pekko.actor.*;
import play.mvc.*;
import scala.jdk.javaapi.FutureConverters;
import javax.inject.*;
import java.util.concurrent.CompletionStage;
import static org.apache.pekko.pattern.Patterns.ask;
@Singleton
public class Application extends Controller {
final ActorRef helloActor;
@Inject
public Application(ActorSystem system) {
helloActor = system.actorOf(HelloActor.getProps());
}
public CompletionStage<Result> sayHello(String name) {
return FutureConverters.asJava(ask(helloActor, new SayHello(name), 1000))
.thenApply(response -> ok((String) response));
}
}
注意すべき点がいくつかあります
- *ask* パターンはインポートする必要があります。`ask` メソッドを静的にインポートするのが最も便利です。
- 返されたfutureは `CompletionStage` に変換されます。結果のpromiseは `CompletionStage<Object>` なので、その値にアクセスするときは、アクターから返される expected type にキャストする必要があります。
- *ask* パターンにはタイムアウトが必要です。ここでは1000ミリ秒を指定しています。アクターが応答するのにそれ以上の時間がかかると、返されたpromiseはタイムアウトエラーで完了します。
- コンストラクターでアクターを作成しているので、コントローラーのスコープを `Singleton` として指定する必要があります。これにより、このコントローラーが使用されるたびに新しいアクターが作成されるのを防ぎます。
§アクターの依存性注入
必要に応じて、Guiceにアクターをインスタンス化させ、コントローラーとコンポーネントが依存するようにアクターrefをバインドさせることができます。
たとえば、Play設定に依存するアクターが必要な場合は、次のようにします。
import com.typesafe.config.Config;
import javax.inject.Inject;
import org.apache.pekko.actor.AbstractActor;
public class ConfiguredActor extends AbstractActor {
private Config configuration;
@Inject
public ConfiguredActor(Config configuration) {
this.configuration = configuration;
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
ConfiguredActorProtocol.GetConfig.class,
message -> {
sender().tell(configuration.getString("my.config"), self());
})
.build();
}
}
Playは、アクターのバインディングを提供するのに役立つヘルパーを提供します。これらにより、アクター自体に依存関係を注入し、アクターのアクターrefを他のコンポーネントに注入できます。これらのヘルパーを使用してアクターをバインドするには、依存性注入のドキュメントに記載されているようにモジュールを作成し、`PekkoGuiceSupport` インターフェースをミックスインして、`bindActor` メソッドを使用してアクターをバインドします。
import com.google.inject.AbstractModule;
import play.libs.pekko.PekkoGuiceSupport;
public class MyModule extends AbstractModule implements PekkoGuiceSupport {
@Override
protected void configure() {
bindActor(ConfiguredActor.class, "configured-actor");
}
}
このアクターは、`configured-actor` という名前になり、注入のために `configured-actor` 名で修飾されます。これで、コントローラーや他のコンポーネントでアクターに依存できるようになりました。
import org.apache.pekko.actor.ActorRef;
import play.mvc.*;
import scala.jdk.javaapi.FutureConverters;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.concurrent.CompletionStage;
import static org.apache.pekko.pattern.Patterns.ask;
public class Application extends Controller {
private ActorRef configuredActor;
@Inject
public Application(@Named("configured-actor") ActorRef configuredActor) {
this.configuredActor = configuredActor;
}
public CompletionStage<Result> getConfig() {
return FutureConverters.asJava(
ask(configuredActor, new ConfiguredActorProtocol.GetConfig(), 1000))
.thenApply(response -> ok((String) response));
}
}
§子アクターの依存性注入
上記はルートアクターの注入には適していますが、作成するアクターの多くは、Playアプリのライフサイクルにバインドされていない子アクターであり、追加の状態が渡される場合があります。
子アクターの依存性注入を支援するために、PlayはGuiceの AssistedInject サポートを利用しています。
注入される設定とキーに依存する、次のようなアクターがあるとします。
import com.google.inject.assistedinject.Assisted;
import com.typesafe.config.Config;
import javax.inject.Inject;
import org.apache.pekko.actor.AbstractActor;
public class ConfiguredChildActor extends AbstractActor {
private final Config configuration;
private final String key;
@Inject
public ConfiguredChildActor(Config configuration, @Assisted String key) {
this.configuration = configuration;
this.key = key;
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(ConfiguredChildActorProtocol.GetConfig.class, this::getConfig)
.build();
}
private void getConfig(ConfiguredChildActorProtocol.GetConfig get) {
sender().tell(configuration.getString(key), self());
}
}
この場合、コンストラクターインジェクションを使用しました。Guiceのassisted injectサポートは、コンストラクターインジェクションとのみ互換性があります。 `key` パラメーターは、コンテナではなく作成時に提供されるため、`@Assisted` でアノテーションを付けました。
次に、子のプロトコルで、`key` を受け取り、`Actor` を返す `Factory` インターフェースを定義します。
import org.apache.pekko.actor.Actor;
public class ConfiguredChildActorProtocol {
public static class GetConfig {}
public interface Factory {
public Actor create(String key);
}
}
これを実装しません。Guiceが実装を提供します。`key` パラメーターを渡すだけでなく、`Configuration` 依存関係を見つけて注入する実装を提供します。traitは `Actor` を返すだけなので、このアクターをテストするときは、任意のアクターを返すファクターを注入できます。たとえば、これにより、実際の子アクターではなく、モックされた子アクターを注入できます。
これで、これに依存するアクターは `InjectedActorSupport` を拡張でき、作成したファクトリーに依存できます。
import javax.inject.Inject;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.actor.ActorRef;
import play.libs.pekko.InjectedActorSupport;
public class ParentActor extends AbstractActor implements InjectedActorSupport {
private ConfiguredChildActorProtocol.Factory childFactory;
@Inject
public ParentActor(ConfiguredChildActorProtocol.Factory childFactory) {
this.childFactory = childFactory;
}
@Override
public Receive createReceive() {
return receiveBuilder().match(ParentActorProtocol.GetChild.class, this::getChild).build();
}
private void getChild(ParentActorProtocol.GetChild msg) {
String key = msg.key;
ActorRef child = injectedChild(() -> childFactory.create(key), key);
sender().tell(child, self());
}
}
`injectedChild` を使用して、キーを渡して子アクターを作成し、参照を取得します。2番目のパラメーター(この例では `key`)は、子アクターの名前として使用されます。
最後に、アクターをバインドする必要があります。モジュールでは、`bindActorFactory` メソッドを使用して親アクターをバインドし、子ファクトリーを子実装にバインドします。
import com.google.inject.AbstractModule;
import play.libs.pekko.PekkoGuiceSupport;
public class MyModule extends AbstractModule implements PekkoGuiceSupport {
@Override
protected void configure() {
bindActor(ParentActor.class, "parent-actor");
bindActorFactory(ConfiguredChildActor.class, ConfiguredChildActorProtocol.Factory.class);
}
}
これにより、Guiceは `ConfiguredChildActorProtocol.Factory` のインスタンスを自動的にバインドします。これは、インスタンス化されるときに `ConfiguredChildActor` に `Configuration` のインスタンスを提供します。
§設定
デフォルトのアクターシステム設定は、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はアプリケーションクラスを見つけることができません。
- Playのアクターシステムですでに使用されているデフォルトの `pekko` 設定からPekko設定を読み取らないでください。これは、システムが同じリモートポートにバインドしようとした場合などに問題が発生する原因となります。
§コードブロックの非同期実行
Pekkoの一般的なユースケースは、アクターの追加ユーティリティを必要とせずに、一部の計算を同時に実行することです。並列で計算を実行するためだけにアクターのプールを作成していることに気付いた場合、より簡単(かつ高速)な方法があります。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import play.mvc.*;
public class Application extends Controller {
public CompletionStage<Result> index() {
return CompletableFuture.supplyAsync(this::longComputation)
.thenApply((Integer i) -> ok("Got " + i));
}
}
§Pekko協調シャットダウン
Playは、Pekkoの 協調シャットダウンを使用して、`Application` と `Server` のシャットダウンを処理します。詳細については、協調シャットダウンの共通セクションを参照してください。
注: Playは、内部 `ActorSystem` のシャットダウンのみを処理します。追加のアクターシステムを使用している場合は、すべて終了していることを確認し、終了コードを 協調シャットダウンに移行してください。
§Pekkoクラスター
Play アプリケーションを既存の Pekko クラスタ に参加させることができます。その場合、クラスタから正常に離脱することが推奨されます。Play には Pekko の Coordinated Shutdown が付属しており、正常な離脱を処理します。
既にカスタムのクラスタ離脱コードがある場合は、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 がどのように eviction を行うかについての詳細はこちら を参照してください。
次: メッセージによる国際化
このドキュメントに誤りを見つけましたか?このページのソースコードは こちら にあります。ドキュメントガイドライン を読んだ後、プルリクエストを送信してください。質問やアドバイスがあれば、コミュニティフォーラム にアクセスして、コミュニティとの会話を始めてください。