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]

Reply via email to