better Typed HelloWorld, #24491
* single actor hello world is too unrealistic and shows the wrong things * although this is longer I think it's better java and feedback
This commit is contained in:
parent
4b54941947
commit
054573e7e1
7 changed files with 182 additions and 117 deletions
|
|
@ -5,21 +5,19 @@
|
|||
package jdocs.akka.typed;
|
||||
|
||||
//#imports
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.ActorSystem;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.Terminated;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.AskPattern;
|
||||
import akka.util.Timeout;
|
||||
|
||||
//#imports
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class IntroTest {
|
||||
|
||||
|
|
@ -42,35 +40,81 @@ public class IntroTest {
|
|||
|
||||
public static final class Greeted {
|
||||
public final String whom;
|
||||
public final ActorRef<Greet> from;
|
||||
|
||||
public Greeted(String whom) {
|
||||
public Greeted(String whom, ActorRef<Greet> from) {
|
||||
this.whom = whom;
|
||||
this.from = from;
|
||||
}
|
||||
}
|
||||
|
||||
public static final Behavior<Greet> greeter = Behaviors.receive((ctx, msg) -> {
|
||||
System.out.println("Hello " + msg.whom + "!");
|
||||
msg.replyTo.tell(new Greeted(msg.whom));
|
||||
ctx.getLog().info("Hello {}!", msg.whom);
|
||||
msg.replyTo.tell(new Greeted(msg.whom, ctx.getSelf()));
|
||||
return Behaviors.same();
|
||||
});
|
||||
}
|
||||
//#hello-world-actor
|
||||
|
||||
public static void main(String[] args) {
|
||||
//#hello-world
|
||||
final ActorSystem<HelloWorld.Greet> system =
|
||||
ActorSystem.create(HelloWorld.greeter, "hello");
|
||||
//#hello-world-bot
|
||||
public abstract static class HelloWorldBot {
|
||||
private HelloWorldBot() {
|
||||
}
|
||||
|
||||
final CompletionStage<HelloWorld.Greeted> reply =
|
||||
AskPattern.ask(system,
|
||||
(ActorRef<HelloWorld.Greeted> replyTo) -> new HelloWorld.Greet("world", replyTo),
|
||||
new Timeout(3, TimeUnit.SECONDS), system.scheduler());
|
||||
|
||||
reply.thenAccept(greeting -> {
|
||||
System.out.println("result: " + greeting.whom);
|
||||
system.terminate();
|
||||
public static final Behavior<HelloWorld.Greeted> bot(int greetingCounter, int max) {
|
||||
return Behaviors.receive((ctx, msg) -> {
|
||||
int n = greetingCounter + 1;
|
||||
ctx.getLog().info("Greeting {} for {}", n, msg.whom);
|
||||
if (n == max) {
|
||||
return Behaviors.stopped();
|
||||
} else {
|
||||
msg.from.tell(new HelloWorld.Greet(msg.whom, ctx.getSelf()));
|
||||
return bot(n, max);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
//#hello-world-bot
|
||||
|
||||
//#hello-world-main
|
||||
public abstract static class HelloWorldMain {
|
||||
private HelloWorldMain() {
|
||||
}
|
||||
|
||||
public static class Start {
|
||||
public final String name;
|
||||
|
||||
public Start(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public static final Behavior<Start> main =
|
||||
Behaviors.setup( context -> {
|
||||
final ActorRef<HelloWorld.Greet> greeter =
|
||||
context.spawn(HelloWorld.greeter, "greeter");
|
||||
|
||||
return Behaviors.receiveMessage(msg -> {
|
||||
ActorRef<HelloWorld.Greeted> replyTo =
|
||||
context.spawn(HelloWorldBot.bot(0, 3), msg.name);
|
||||
greeter.tell(new HelloWorld.Greet(msg.name, replyTo));
|
||||
return Behaviors.same();
|
||||
});
|
||||
});
|
||||
}
|
||||
//#hello-world-main
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
//#hello-world
|
||||
final ActorSystem<HelloWorldMain.Start> system =
|
||||
ActorSystem.create(HelloWorldMain.main, "hello");
|
||||
|
||||
system.tell(new HelloWorldMain.Start("World"));
|
||||
system.tell(new HelloWorldMain.Start("Akka"));
|
||||
//#hello-world
|
||||
|
||||
Thread.sleep(3000);
|
||||
system.terminate();
|
||||
}
|
||||
|
||||
//#chatroom-actor
|
||||
|
|
|
|||
|
|
@ -5,19 +5,17 @@
|
|||
package docs.akka.typed
|
||||
|
||||
//#imports
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import akka.NotUsed
|
||||
import akka.actor.typed.scaladsl.AskPattern._
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import akka.actor.typed.{ ActorRef, ActorSystem, Behavior, Terminated }
|
||||
import akka.testkit.typed.scaladsl.ActorTestKit
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ Await, Future }
|
||||
//#imports
|
||||
|
||||
import akka.testkit.typed.scaladsl.ActorTestKit
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import akka.actor.typed.TypedAkkaSpecWithShutdown
|
||||
|
||||
object IntroSpec {
|
||||
|
|
@ -25,16 +23,51 @@ object IntroSpec {
|
|||
//#hello-world-actor
|
||||
object HelloWorld {
|
||||
final case class Greet(whom: String, replyTo: ActorRef[Greeted])
|
||||
final case class Greeted(whom: String)
|
||||
final case class Greeted(whom: String, from: ActorRef[Greet])
|
||||
|
||||
val greeter = Behaviors.receive[Greet] { (_, msg) ⇒
|
||||
println(s"Hello ${msg.whom}!")
|
||||
msg.replyTo ! Greeted(msg.whom)
|
||||
val greeter: Behavior[Greet] = Behaviors.receive { (ctx, msg) ⇒
|
||||
ctx.log.info("Hello {}!", msg.whom)
|
||||
msg.replyTo ! Greeted(msg.whom, ctx.self)
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
//#hello-world-actor
|
||||
|
||||
//#hello-world-bot
|
||||
object HelloWorldBot {
|
||||
|
||||
def bot(greetingCounter: Int, max: Int): Behavior[HelloWorld.Greeted] =
|
||||
Behaviors.receive { (ctx, msg) ⇒
|
||||
val n = greetingCounter + 1
|
||||
ctx.log.info("Greeting {} for {}", n, msg.whom)
|
||||
if (n == max) {
|
||||
Behaviors.stopped
|
||||
} else {
|
||||
msg.from ! HelloWorld.Greet(msg.whom, ctx.self)
|
||||
bot(n, max)
|
||||
}
|
||||
}
|
||||
}
|
||||
//#hello-world-bot
|
||||
|
||||
//#hello-world-main
|
||||
object HelloWorldMain {
|
||||
|
||||
final case class Start(name: String)
|
||||
|
||||
val main: Behavior[Start] =
|
||||
Behaviors.setup { context ⇒
|
||||
val greeter = context.spawn(HelloWorld.greeter, "greeter")
|
||||
|
||||
Behaviors.receiveMessage { msg ⇒
|
||||
val replyTo = context.spawn(HelloWorldBot.bot(greetingCounter = 0, max = 3), msg.name)
|
||||
greeter ! HelloWorld.Greet(msg.name, replyTo)
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
}
|
||||
//#hello-world-main
|
||||
|
||||
//#chatroom-actor
|
||||
object ChatRoom {
|
||||
//#chatroom-protocol
|
||||
|
|
@ -63,7 +96,7 @@ object IntroSpec {
|
|||
chatRoom(List.empty)
|
||||
|
||||
private def chatRoom(sessions: List[ActorRef[SessionCommand]]): Behavior[RoomCommand] =
|
||||
Behaviors.receive[RoomCommand] { (ctx, msg) ⇒
|
||||
Behaviors.receive { (ctx, msg) ⇒
|
||||
msg match {
|
||||
case GetSession(screenName, client) ⇒
|
||||
// create a child actor for further interaction with the client
|
||||
|
|
@ -107,33 +140,26 @@ class IntroSpec extends ActorTestKit with TypedAkkaSpecWithShutdown {
|
|||
|
||||
"Hello world" must {
|
||||
"say hello" in {
|
||||
// TODO Implicits.global is not something we would like to encourage in docs
|
||||
//#hello-world
|
||||
import HelloWorld._
|
||||
// using global pool since we want to run tasks after system.terminate
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
val system: ActorSystem[Greet] = ActorSystem(greeter, "hello")
|
||||
val system: ActorSystem[HelloWorldMain.Start] =
|
||||
ActorSystem(HelloWorldMain.main, "hello")
|
||||
|
||||
val future: Future[Greeted] = system ? (Greet("world", _))
|
||||
system ! HelloWorldMain.Start("World")
|
||||
system ! HelloWorldMain.Start("Akka")
|
||||
|
||||
for {
|
||||
greeting ← future.recover { case ex ⇒ ex.getMessage }
|
||||
done ← {
|
||||
println(s"result: $greeting")
|
||||
system.terminate()
|
||||
}
|
||||
} println("system terminated")
|
||||
//#hello-world
|
||||
|
||||
Thread.sleep(500) // it will not fail if too short
|
||||
ActorTestKit.shutdown(system)
|
||||
}
|
||||
|
||||
"chat" in {
|
||||
//#chatroom-gabbler
|
||||
import ChatRoom._
|
||||
|
||||
val gabbler =
|
||||
Behaviors.receive[SessionEvent] { (_, msg) ⇒
|
||||
msg match {
|
||||
val gabbler: Behavior[SessionEvent] =
|
||||
Behaviors.receiveMessage {
|
||||
//#chatroom-gabbler
|
||||
// We document that the compiler warns about the missing handler for `SessionDenied`
|
||||
case SessionDenied(reason) ⇒
|
||||
|
|
@ -147,7 +173,6 @@ class IntroSpec extends ActorTestKit with TypedAkkaSpecWithShutdown {
|
|||
println(s"message has been posted by '$screenName': $message")
|
||||
Behaviors.stopped
|
||||
}
|
||||
}
|
||||
//#chatroom-gabbler
|
||||
|
||||
//#chatroom-main
|
||||
|
|
|
|||
|
|
@ -211,11 +211,11 @@ object Behaviors {
|
|||
* final Behavior[DbCommand] dbConnector = ...
|
||||
*
|
||||
* final Behavior[DbCommand] dbRestarts =
|
||||
* Actor.supervise(dbConnector)
|
||||
* Behaviors.supervise(dbConnector)
|
||||
* .onFailure(SupervisorStrategy.restart) // handle all NonFatal exceptions
|
||||
*
|
||||
* final Behavior[DbCommand] dbSpecificResumes =
|
||||
* Actor.supervise(dbConnector)
|
||||
* Behaviors.supervise(dbConnector)
|
||||
* .onFailure[IndexOutOfBoundsException](SupervisorStrategy.resume) // resume for IndexOutOfBoundsException exceptions
|
||||
* }}}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import scala.concurrent.duration.FiniteDuration
|
|||
|
||||
/**
|
||||
* Support for scheduled `self` messages in an actor.
|
||||
* It is used with `Actor.withTimers`, which also takes care of the
|
||||
* It is used with `Behaviors.withTimers`, which also takes care of the
|
||||
* lifecycle of the timers such as cancelling them when the actor
|
||||
* is restarted or stopped.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -171,11 +171,11 @@ object Behaviors {
|
|||
* val dbConnector: Behavior[DbCommand] = ...
|
||||
*
|
||||
* val dbRestarts =
|
||||
* Actor.supervise(dbConnector)
|
||||
* Behaviors.supervise(dbConnector)
|
||||
* .onFailure(SupervisorStrategy.restart) // handle all NonFatal exceptions
|
||||
*
|
||||
* val dbSpecificResumes =
|
||||
* Actor.supervise(dbConnector)
|
||||
* Behaviors.supervise(dbConnector)
|
||||
* .onFailure[IndexOutOfBoundsException](SupervisorStrategy.resume) // resume for IndexOutOfBoundsException exceptions
|
||||
* }}}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import scala.concurrent.duration.FiniteDuration
|
|||
|
||||
/**
|
||||
* Support for scheduled `self` messages in an actor.
|
||||
* It is used with `Actor.withTimers`.
|
||||
* It is used with `Behaviors.withTimers`.
|
||||
* Timers are bound to the lifecycle of the actor that owns it,
|
||||
* and thus are cancelled automatically when it is restarted or stopped.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ The behavior of the Actor is defined as the `greeter` value with the help
|
|||
of the `receive` behavior factory. Processing the next message then results
|
||||
in a new behavior that can potentially be different from this one. State is
|
||||
updated by returning a new behavior that holds the new immutable state. In this
|
||||
case we don't need to update any state, so we return `Same`, which means
|
||||
case we don't need to update any state, so we return `same`, which means
|
||||
the next behavior is "the same as the current one".
|
||||
|
||||
The type of the messages handled by this behavior is declared to be of class
|
||||
|
|
@ -73,6 +73,31 @@ protocol but Actors can model arbitrarily complex protocols when needed. The
|
|||
protocol is bundled together with the behavior that implements it in a nicely
|
||||
wrapped scope—the `HelloWorld` @scala[object]@java[class].
|
||||
|
||||
As Carl Hewitt said, one Actor is no Actor—it would be quite lonely with
|
||||
nobody to talk to. We need another Actor that that interacts with the `greeter`.
|
||||
Let's make a `bot` that receives the reply from the `greeter` and sends a number
|
||||
of additional greeting messages and collect the replies until a given max number
|
||||
of messages have been reached.
|
||||
|
||||
Scala
|
||||
: @@snip [IntroSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #hello-world-bot }
|
||||
|
||||
Java
|
||||
: @@snip [IntroSpec.scala]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #hello-world-bot }
|
||||
|
||||
Note how this Actor manages the counter by changing the behavior for each `Greeted` reply
|
||||
rather than using any variables.
|
||||
|
||||
|
||||
|
||||
A third actor spawns the `greeter` and the `bot` and starts the interaction between those.
|
||||
|
||||
Scala
|
||||
: @@snip [IntroSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #hello-world-main }
|
||||
|
||||
Java
|
||||
: @@snip [IntroSpec.scala]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #hello-world-main }
|
||||
|
||||
Now we want to try out this Actor, so we must start an ActorSystem to host it:
|
||||
|
||||
Scala
|
||||
|
|
@ -81,58 +106,29 @@ Scala
|
|||
Java
|
||||
: @@snip [IntroSpec.scala]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #hello-world }
|
||||
|
||||
After importing the Actor’s protocol definition we start an Actor system from
|
||||
the defined `greeter` behavior.
|
||||
We start an Actor system from the defined `main` behavior and send two `Start` messages that
|
||||
will kick-off the interaction between two separate `bot` actors and the single `greeter` actor.
|
||||
|
||||
As Carl Hewitt said, one Actor is no Actor—it would be quite lonely with
|
||||
nobody to talk to. In this sense the example is a little cruel because we only
|
||||
give the `HelloWorld` Actor a fake person to talk to—the “ask” pattern
|
||||
(represented by the `?` operator) can be used to send a message such that the
|
||||
reply fulfills a @scala[`Promise` to which we get back the corresponding `Future`]@java[`CompletionStage`].
|
||||
The console output may look like this:
|
||||
|
||||
@@@ div {.group-scala}
|
||||
|
||||
Note that the `Future` that is returned by the “ask” operation is
|
||||
properly typed already, no type checks or casts needed. This is possible due to
|
||||
the type information that is part of the message protocol: the `?` operator
|
||||
takes as argument a function that accepts an `ActorRef[U]` (which
|
||||
explains the `_` hole in the expression on line 7 above) and the `replyTo`
|
||||
parameter which we fill in is of type `ActorRef[Greeted]`, which
|
||||
means that the value that fulfills the `Promise` can only be of type
|
||||
`Greeted`.
|
||||
|
||||
@@@
|
||||
|
||||
@@@ div {.group-java}
|
||||
|
||||
Note that the `CompletionStage` that is returned by the “ask” operation is
|
||||
properly typed already, no type checks or casts needed. This is possible due to
|
||||
the type information that is part of the message protocol: the `ask` operator
|
||||
takes as argument a function that pass an `ActorRef<U>`, which is the
|
||||
`replyTo` parameter of the `Greet` message, which means that when sending
|
||||
the reply message to that `ActorRef` the message that fulfills the
|
||||
`CompletionStage` can only be of type `Greeted`.
|
||||
|
||||
@@@
|
||||
|
||||
We use this here to send the `Greet` command to the Actor and when the
|
||||
reply comes back we will print it out and tell the actor system to shut down.
|
||||
|
||||
@@@ div {.group-scala}
|
||||
|
||||
The `recovery` combinator on the original `Future` is
|
||||
needed in order to ensure proper system shutdown even in case something went
|
||||
wrong; the `flatMap` and `map` combinators that the `for` expression gets
|
||||
turned into care only about the “happy path” and if the `future` failed with
|
||||
a timeout then no `greeting` would be extracted and nothing would happen.
|
||||
|
||||
@@@
|
||||
|
||||
In the next section we demonstrate this on a more realistic example.
|
||||
```
|
||||
[INFO] [03/13/2018 15:50:05.814] [hello-akka.actor.default-dispatcher-4] [akka://hello/user/greeter] Hello World!
|
||||
[INFO] [03/13/2018 15:50:05.815] [hello-akka.actor.default-dispatcher-4] [akka://hello/user/greeter] Hello Akka!
|
||||
[INFO] [03/13/2018 15:50:05.815] [hello-akka.actor.default-dispatcher-2] [akka://hello/user/World] Greeting 1 for World
|
||||
[INFO] [03/13/2018 15:50:05.815] [hello-akka.actor.default-dispatcher-4] [akka://hello/user/Akka] Greeting 1 for Akka
|
||||
[INFO] [03/13/2018 15:50:05.815] [hello-akka.actor.default-dispatcher-5] [akka://hello/user/greeter] Hello World!
|
||||
[INFO] [03/13/2018 15:50:05.815] [hello-akka.actor.default-dispatcher-5] [akka://hello/user/greeter] Hello Akka!
|
||||
[INFO] [03/13/2018 15:50:05.815] [hello-akka.actor.default-dispatcher-4] [akka://hello/user/World] Greeting 2 for World
|
||||
[INFO] [03/13/2018 15:50:05.815] [hello-akka.actor.default-dispatcher-5] [akka://hello/user/greeter] Hello World!
|
||||
[INFO] [03/13/2018 15:50:05.815] [hello-akka.actor.default-dispatcher-4] [akka://hello/user/Akka] Greeting 2 for Akka
|
||||
[INFO] [03/13/2018 15:50:05.816] [hello-akka.actor.default-dispatcher-5] [akka://hello/user/greeter] Hello Akka!
|
||||
[INFO] [03/13/2018 15:50:05.816] [hello-akka.actor.default-dispatcher-4] [akka://hello/user/World] Greeting 3 for World
|
||||
[INFO] [03/13/2018 15:50:05.816] [hello-akka.actor.default-dispatcher-6] [akka://hello/user/Akka] Greeting 3 for Akka
|
||||
```
|
||||
|
||||
## A More Complex Example
|
||||
|
||||
The next example demonstrates some important patterns:
|
||||
The next example is more realistic and demonstrates some important patterns:
|
||||
|
||||
* Using a sealed trait and case class/objects to represent multiple messages an actor can receive
|
||||
* Handle sessions by using child actors
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue