This is an automated email from the ASF dual-hosted git repository.
pjfanning pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko-grpc.git
The following commit(s) were added to refs/heads/main by this push:
new 1788983a added typed stateful helloworld and
docs/.../server/walkthrough (#702)
1788983a is described below
commit 1788983a9a31571c4f5de669288825cb84e8eede
Author: Kazuhiro Funakoshi <[email protected]>
AuthorDate: Mon May 18 15:45:30 2026 -0700
added typed stateful helloworld and docs/.../server/walkthrough (#702)
* added typed stateful helloworld and docs/.../server/walkthrough
* added java typedhelloworld and updated docs
* fixed Gradle tests and Scalafmt issue
* fixed pom for mvn test and FQCN of ActorSystem for paradox test
* fixed gradle/mvn test in plugin-tester-scala, removed redundant blank line
* avoid direct URLs to pekko docs - use extref
* Revise recommendation for typed actor usage
Updated recommendation for using typed actors in the walkthrough.
---------
Co-authored-by: PJ Fanning <[email protected]>
---
build.sbt | 2 +
docs/src/main/paradox/server/pekko-http.md | 2 +-
docs/src/main/paradox/server/walkthrough.md | 22 +++++++
plugin-tester-java/build.gradle | 1 +
plugin-tester-java/pom.xml | 6 ++
.../myapp/typedhelloworld/GreeterActor.java | 77 ++++++++++++++++++++++
.../myapp/typedhelloworld/GreeterServiceImpl.java | 55 ++++++++++++++++
plugin-tester-scala/build.gradle | 1 +
plugin-tester-scala/pom.xml | 5 ++
.../myapp/typedhelloworld/GreeterActor.scala | 46 +++++++++++++
.../myapp/typedhelloworld/GreeterServiceImpl.scala | 44 +++++++++++++
11 files changed, 260 insertions(+), 1 deletion(-)
diff --git a/build.sbt b/build.sbt
index d89bbbdd..ac11a373 100644
--- a/build.sbt
+++ b/build.sbt
@@ -301,6 +301,7 @@ lazy val pluginTesterScala = Project(id =
"plugin-tester-scala", base = file("pl
.addPekkoModuleDependency("pekko-http-cors", "", PekkoHttpDependency.default)
.addPekkoModuleDependency("pekko-http", "", PekkoHttpDependency.default)
.addPekkoModuleDependency("pekko-pki", "", PekkoCoreDependency.default)
+ .addPekkoModuleDependency("pekko-actor-typed", "",
PekkoCoreDependency.default)
.addPekkoModuleDependency("pekko-actor-testkit-typed", "test",
PekkoCoreDependency.default)
.settings(Dependencies.pluginTester)
.settings(
@@ -316,6 +317,7 @@ lazy val pluginTesterScala = Project(id =
"plugin-tester-scala", base = file("pl
lazy val pluginTesterJava = Project(id = "plugin-tester-java", base =
file("plugin-tester-java"))
.disablePlugins(MimaPlugin)
.addPekkoModuleDependency("pekko-pki", "", PekkoCoreDependency.default)
+ .addPekkoModuleDependency("pekko-actor-typed", "",
PekkoCoreDependency.default)
.settings(Dependencies.pluginTester)
.settings(
name := s"$pekkoPrefix-plugin-tester-java",
diff --git a/docs/src/main/paradox/server/pekko-http.md
b/docs/src/main/paradox/server/pekko-http.md
index a2e86f8c..ebae96e7 100644
--- a/docs/src/main/paradox/server/pekko-http.md
+++ b/docs/src/main/paradox/server/pekko-http.md
@@ -85,7 +85,7 @@ We define a method that returns a @apidoc[Route$]
implementing our logging and e
The method takes three parameters.
1. A function that takes a @apidoc[RequestContext] and returns a service
implementation. This gives us the opportunity to use the context in the
implementation. If we don't need it, we can just ignore the context and return
a fixed implementation.
-2. A function that takes an @apidoc[ActorSystem] and returns a partial
function from Throwable to gRPC @apidoc[Trailers].
+2. A function that takes an @apidoc[org.apache.pekko.actor.ActorSystem] and
returns a partial function from Throwable to gRPC @apidoc[Trailers].
3. A function that takes the service implementation and an error handler and
returns a request handler (a function from @apidoc[HttpRequest] to a
@scala[`Future`]@java[`CompletionStage`] of @apidoc[HttpResponse]).
The method first uses an existing directive to log requests and results.
diff --git a/docs/src/main/paradox/server/walkthrough.md
b/docs/src/main/paradox/server/walkthrough.md
index 59b9366e..f1a84da7 100644
--- a/docs/src/main/paradox/server/walkthrough.md
+++ b/docs/src/main/paradox/server/walkthrough.md
@@ -253,4 +253,26 @@ Scala
Java
: @@snip
[GreeterActor.java](/plugin-tester-java/src/main/java/example/myapp/statefulhelloworld/GreeterActor.java)
{ #actor }
+We now recommend using typed actors. In the following example,
+we can make it sure that Hello World actor receives commands that are only
+derived from `GreeterActor.GreetingCommand`.
+
+To learn more about difference between typed actor and classic, please refer
to @extref:[Learning Pekko Typed from Classic](pekko:typed/from-classic.html).
+
+Scala
+: @@snip
[GreeterServiceImpl.scala](/plugin-tester-scala/src/main/scala/example/myapp/typedhelloworld/GreeterServiceImpl.scala)
{ #stateful-service }
+
+Java
+: @@snip
[GreeterServiceImpl.java](/plugin-tester-java/src/main/java/example/myapp/typedhelloworld/GreeterServiceImpl.java)
{ #stateful-service }
+
+The typed version of `GreeterActor` is implemented like this:
+
+Scala
+: @@snip
[GreeterActor.scala](/plugin-tester-scala/src/main/scala/example/myapp/typedhelloworld/GreeterActor.scala)
{ #actor }
+
+Java
+: @@snip
[GreeterActor.java](/plugin-tester-java/src/main/java/example/myapp/typedhelloworld/GreeterActor.java)
{ #actor }
+
+
Now the actor mailbox is used to synchronize accesses to the mutable state.
+
diff --git a/plugin-tester-java/build.gradle b/plugin-tester-java/build.gradle
index b5f1dff1..91365736 100644
--- a/plugin-tester-java/build.gradle
+++ b/plugin-tester-java/build.gradle
@@ -33,6 +33,7 @@ def pekkoHttpVersion = "2.0.0-M1"
dependencies {
implementation
"org.apache.pekko:pekko-http-cors_${scalaBinaryVersion}:${pekkoHttpVersion}"
implementation
"org.apache.pekko:pekko-pki_${scalaBinaryVersion}:${pekkoVersion}"
+ implementation
"org.apache.pekko:pekko-actor-typed_${scalaBinaryVersion}:${pekkoVersion}"
implementation "org.scala-lang:scala-library:${scalaFullVersion}"
testImplementation "org.scalatest:scalatest_${scalaBinaryVersion}:3.2.20"
testImplementation
"org.scalatestplus:junit-4-13_${scalaBinaryVersion}:3.2.20.0"
diff --git a/plugin-tester-java/pom.xml b/plugin-tester-java/pom.xml
index ac9dc142..20137b0b 100644
--- a/plugin-tester-java/pom.xml
+++ b/plugin-tester-java/pom.xml
@@ -65,6 +65,12 @@
<artifactId>pekko-pki_2.13</artifactId>
<version>${pekko.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.pekko</groupId>
+ <artifactId>pekko-actor-typed_2.13</artifactId>
+ <version>${pekko.version}</version>
+ </dependency>
+
<!-- Needed for the generated client -->
<dependency>
diff --git
a/plugin-tester-java/src/main/java/example/myapp/typedhelloworld/GreeterActor.java
b/plugin-tester-java/src/main/java/example/myapp/typedhelloworld/GreeterActor.java
new file mode 100644
index 00000000..62e04303
--- /dev/null
+++
b/plugin-tester-java/src/main/java/example/myapp/typedhelloworld/GreeterActor.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * license agreements; and to You under the Apache License, version 2.0:
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * This file is part of the Apache Pekko project, which was derived from Akka.
+ */
+
+/*
+ * Copyright (C) 2018-2021 Lightbend Inc. <https://www.lightbend.com>
+ */
+
+package example.myapp.typedhelloworld;
+
+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;
+
+// #actor
+public class GreeterActor extends
AbstractBehavior<GreeterActor.GreetingCommand> {
+
+ public static interface GreetingCommand {}
+ public static class ChangeGreeting implements GreetingCommand{
+ public final String newGreeting;
+
+ public ChangeGreeting(String newGreeting) {
+ this.newGreeting = newGreeting;
+ }
+ }
+
+ public static class GetGreeting implements GreetingCommand {
+ public final ActorRef<Greeting> replyTo;
+
+ public GetGreeting(ActorRef<Greeting> replyTo) {
+ this.replyTo = replyTo;
+ }
+ }
+
+ public static class Greeting {
+ public final String greeting;
+
+ public Greeting(String greeting) {
+ this.greeting = greeting;
+ }
+ }
+
+ public static Behavior<GreetingCommand> create(final String initialGreeting)
{
+ return Behaviors.setup(context -> new GreeterActor(context,
initialGreeting));
+ }
+ private GreeterActor(ActorContext<GreetingCommand> context, String
initialGreeting) {
+ super(context);
+ }
+
+ private Greeting greeting;
+
+ public Receive<GreetingCommand> createReceive() {
+ return newReceiveBuilder()
+ .onMessage(GetGreeting.class, this::onGetGreeting)
+ .onMessage(ChangeGreeting.class, this::onChangeGreeting)
+ .build();
+ }
+
+ private Behavior<GreetingCommand> onGetGreeting(GetGreeting get) {
+ get.replyTo.tell(greeting);
+ return this;
+ }
+
+ private Behavior<GreetingCommand> onChangeGreeting(ChangeGreeting change) {
+ greeting = new Greeting(change.newGreeting);
+ return this;
+ }
+}
+// #actor
diff --git
a/plugin-tester-java/src/main/java/example/myapp/typedhelloworld/GreeterServiceImpl.java
b/plugin-tester-java/src/main/java/example/myapp/typedhelloworld/GreeterServiceImpl.java
new file mode 100644
index 00000000..7fd74494
--- /dev/null
+++
b/plugin-tester-java/src/main/java/example/myapp/typedhelloworld/GreeterServiceImpl.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * license agreements; and to You under the Apache License, version 2.0:
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * This file is part of the Apache Pekko project, which was derived from Akka.
+ */
+
+/*
+ * Copyright (C) 2018-2021 Lightbend Inc. <https://www.lightbend.com>
+ */
+
+package example.myapp.typedhelloworld;
+
+import example.myapp.statefulhelloworld.grpc.*;
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import org.apache.pekko.actor.typed.ActorRef;
+import org.apache.pekko.actor.typed.ActorSystem;
+import org.apache.pekko.actor.typed.javadsl.AskPattern;
+
+// #stateful-service
+public final class GreeterServiceImpl implements GreeterService {
+
+ private final ActorSystem system;
+ private final ActorRef<GreeterActor.GreetingCommand> greeterActor;
+
+ public GreeterServiceImpl(ActorSystem system,
ActorRef<GreeterActor.GreetingCommand> greeterActor) {
+ this.system = system;
+ this.greeterActor = greeterActor;
+ }
+
+ public CompletionStage<HelloReply> sayHello(HelloRequest in) {
+ CompletionStage<GreeterActor.Greeting> response = AskPattern.ask(
+ greeterActor,
+ replyTo -> new GreeterActor.GetGreeting(replyTo),
+ Duration.ofSeconds(5),
+ system.scheduler()
+ );
+ return response.thenApply(
+ message ->
+ HelloReply.newBuilder()
+ .setMessage(((GreeterActor.Greeting) message).greeting)
+ .build()
+ );
+ }
+
+ public CompletionStage<ChangeResponse> changeGreeting(ChangeRequest in) {
+ greeterActor.tell(new GreeterActor.ChangeGreeting(in.getNewGreeting()));
+ return
CompletableFuture.completedFuture(ChangeResponse.newBuilder().build());
+ }
+}
+// #stateful-service
diff --git a/plugin-tester-scala/build.gradle b/plugin-tester-scala/build.gradle
index 24d8c4fd..f35d61c9 100644
--- a/plugin-tester-scala/build.gradle
+++ b/plugin-tester-scala/build.gradle
@@ -28,6 +28,7 @@ def pekkoHttpVersion = "2.0.0-M1"
dependencies {
implementation
"org.apache.pekko:pekko-http-cors_${scalaBinaryVersion}:${pekkoHttpVersion}"
implementation
"org.apache.pekko:pekko-pki_${scalaBinaryVersion}:${pekkoVersion}"
+ implementation
"org.apache.pekko:pekko-actor-typed_${scalaBinaryVersion}:${pekkoVersion}"
implementation "org.scala-lang:scala-library:${scalaFullVersion}"
testImplementation
"org.apache.pekko:pekko-discovery_${scalaBinaryVersion}:${pekkoVersion}"
testImplementation
"org.apache.pekko:pekko-actor-testkit-typed_${scalaBinaryVersion}:${pekkoVersion}"
diff --git a/plugin-tester-scala/pom.xml b/plugin-tester-scala/pom.xml
index d409761e..f2500f2b 100644
--- a/plugin-tester-scala/pom.xml
+++ b/plugin-tester-scala/pom.xml
@@ -63,6 +63,11 @@
<artifactId>pekko-pki_2.13</artifactId>
<version>${pekko.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.pekko</groupId>
+ <artifactId>pekko-actor-typed_2.13</artifactId>
+ <version>${pekko.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.pekko</groupId>
diff --git
a/plugin-tester-scala/src/main/scala/example/myapp/typedhelloworld/GreeterActor.scala
b/plugin-tester-scala/src/main/scala/example/myapp/typedhelloworld/GreeterActor.scala
new file mode 100644
index 00000000..5e350c70
--- /dev/null
+++
b/plugin-tester-scala/src/main/scala/example/myapp/typedhelloworld/GreeterActor.scala
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * license agreements; and to You under the Apache License, version 2.0:
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * This file is part of the Apache Pekko project, which was derived from Akka.
+ */
+
+/*
+ * Copyright (C) 2018-2021 Lightbend Inc. <https://www.lightbend.com>
+ */
+
+package example.myapp.typedhelloworld
+
+import org.apache.pekko
+import pekko.actor.typed.scaladsl.Behaviors
+import pekko.actor.typed.{ ActorRef, Behavior }
+
+// #actor
+object GreeterActor {
+ sealed trait GreetingCommand
+ case class ChangeGreeting(newGreeting: String) extends GreetingCommand
+ case class GetGreeting(replyTo: ActorRef[Greeting]) extends GreetingCommand
+
+ case object GetGreeting
+ case class Greeting(greeting: String)
+
+ def apply(initialGreeting: String): Behavior[GreetingCommand] = (new
GreeterActor(initialGreeting)).createBehavior()
+}
+
+class GreeterActor(initialGreeting: String) {
+ import GreeterActor._
+
+ var greeting = Greeting(initialGreeting)
+
+ def createBehavior(): Behavior[GreetingCommand] = Behaviors.receiveMessage {
+ case ChangeGreeting(newGreeting) =>
+ greeting = Greeting(newGreeting)
+ createBehavior()
+ case GetGreeting(replyTo) =>
+ replyTo ! greeting
+ Behaviors.same
+ }
+}
+// #actor
diff --git
a/plugin-tester-scala/src/main/scala/example/myapp/typedhelloworld/GreeterServiceImpl.scala
b/plugin-tester-scala/src/main/scala/example/myapp/typedhelloworld/GreeterServiceImpl.scala
new file mode 100644
index 00000000..09414459
--- /dev/null
+++
b/plugin-tester-scala/src/main/scala/example/myapp/typedhelloworld/GreeterServiceImpl.scala
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * license agreements; and to You under the Apache License, version 2.0:
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * This file is part of the Apache Pekko project, which was derived from Akka.
+ */
+
+/*
+ * Copyright (C) 2018-2021 Lightbend Inc. <https://www.lightbend.com>
+ */
+
+package example.myapp.typedhelloworld
+
+import example.myapp.statefulhelloworld.grpc.GreeterService
+import example.myapp.statefulhelloworld.grpc.{ ChangeRequest, ChangeResponse,
HelloReply, HelloRequest }
+import org.apache.pekko
+import pekko.actor.typed.{ ActorRef, ActorSystem }
+import pekko.actor.typed.scaladsl.AskPattern._
+import pekko.util.Timeout
+import scala.concurrent.duration._
+
+import scala.concurrent.{ ExecutionContext, Future }
+
+// #stateful-service
+class GreeterServiceImpl(greeterActor:
ActorRef[GreeterActor.GreetingCommand])(implicit system: ActorSystem[_])
+ extends GreeterService {
+
+ def sayHello(in: HelloRequest): Future[HelloReply] = {
+ // timeout and execution context for ask
+ implicit val timeout: Timeout = 3.seconds
+ implicit val ec: ExecutionContext = system.executionContext
+
+ greeterActor.ask((replyTo: ActorRef[GreeterActor.Greeting]) =>
GreeterActor.GetGreeting(replyTo))
+ .map(message => HelloReply(s"${message.greeting}, ${in.name}"))
+ }
+
+ def changeGreeting(in: ChangeRequest): Future[ChangeResponse] = {
+ greeterActor ! GreeterActor.ChangeGreeting(in.newGreeting)
+ Future.successful(ChangeResponse())
+ }
+}
+// #stateful-service
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]