doc: Show how to fail future of external ask (#28017)

* doc: Show how to fail future of external ask

* illustrate that validation errors are also part of
  message protocol
* error messages can be turned into failed future

* add implementation of the CookieFabric
This commit is contained in:
Patrik Nordwall 2019-10-17 11:36:41 +02:00 committed by GitHub
parent f914ab259d
commit b863646258
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 120 additions and 19 deletions

View file

@ -620,25 +620,57 @@ public class InteractionPatternsTest extends JUnitSuite {
interface StandaloneAskSample {
// #standalone-ask
public class CookieFabric {
public class CookieFabric extends AbstractBehavior<CookieFabric.Command> {
interface Command {}
public static class GiveMeCookies implements Command {
public final ActorRef<Cookies> cookies;
public final int count;
public final ActorRef<Reply> replyTo;
public GiveMeCookies(ActorRef<Cookies> cookies) {
this.cookies = cookies;
public GiveMeCookies(int count, ActorRef<Reply> replyTo) {
this.count = count;
this.replyTo = replyTo;
}
}
public static class Cookies {
interface Reply {}
public static class Cookies implements Reply {
public final int count;
public Cookies(int count) {
this.count = count;
}
}
public static class InvalidRequest implements Reply {
public final String reason;
public InvalidRequest(String reason) {
this.reason = reason;
}
}
public static Behavior<Command> create() {
return Behaviors.setup(CookieFabric::new);
}
private CookieFabric(ActorContext<Command> context) {
super(context);
}
@Override
public Receive<Command> createReceive() {
return newReceiveBuilder().onMessage(GiveMeCookies.class, this::onGiveMeCookies).build();
}
private Behavior<Command> onGiveMeCookies(GiveMeCookies request) {
if (request.count >= 5) request.replyTo.tell(new InvalidRequest("Too many cookies."));
else request.replyTo.tell(new Cookies(request.count));
return this;
}
}
// #standalone-ask
@ -648,23 +680,59 @@ public class InteractionPatternsTest extends JUnitSuite {
public void askAndPrint(
ActorSystem<Void> system, ActorRef<CookieFabric.Command> cookieFabric) {
CompletionStage<CookieFabric.Cookies> result =
CompletionStage<CookieFabric.Reply> result =
AskPattern.ask(
cookieFabric,
CookieFabric.GiveMeCookies::new,
replyTo -> new CookieFabric.GiveMeCookies(3, replyTo),
// asking someone requires a timeout and a scheduler, if the timeout hits without
// response
// the ask is failed with a TimeoutException
// response the ask is failed with a TimeoutException
Duration.ofSeconds(3),
system.scheduler());
result.whenComplete(
(cookies, failure) -> {
if (cookies != null) System.out.println("Yay, cookies!");
else System.out.println("Boo! didn't get cookies in time.");
(reply, failure) -> {
if (reply instanceof CookieFabric.Cookies)
System.out.println("Yay, " + ((CookieFabric.Cookies) reply).count + " cookies!");
else if (reply instanceof CookieFabric.InvalidRequest)
System.out.println(
"No cookies for me. " + ((CookieFabric.InvalidRequest) reply).reason);
else System.out.println("Boo! didn't get cookies in time. " + failure);
});
}
// #standalone-ask
public void askAndMapInvalid(
ActorSystem<Void> system, ActorRef<CookieFabric.Command> cookieFabric) {
// #standalone-ask-fail-future
CompletionStage<CookieFabric.Reply> result =
AskPattern.ask(
cookieFabric,
replyTo -> new CookieFabric.GiveMeCookies(3, replyTo),
Duration.ofSeconds(3),
system.scheduler());
CompletionStage<CookieFabric.Cookies> cookies =
result.thenCompose(
(CookieFabric.Reply reply) -> {
if (reply instanceof CookieFabric.Cookies) {
return CompletableFuture.completedFuture((CookieFabric.Cookies) reply);
} else if (reply instanceof CookieFabric.InvalidRequest) {
CompletableFuture<CookieFabric.Cookies> failed = new CompletableFuture<>();
failed.completeExceptionally(
new IllegalArgumentException(((CookieFabric.InvalidRequest) reply).reason));
return failed;
} else {
throw new IllegalStateException("Unexpected reply: " + reply.getClass());
}
});
cookies.whenComplete(
(cookiesReply, failure) -> {
if (cookies != null) System.out.println("Yay, " + cookiesReply.count + " cookies!");
else System.out.println("Boo! didn't get cookies in time. " + failure);
});
// #standalone-ask-fail-future
}
}
}

View file

@ -391,12 +391,18 @@ class InteractionPatternsSpec extends ScalaTestWithActorTestKit with WordSpecLik
// #standalone-ask
object CookieFabric {
sealed trait Command {}
case class GiveMeCookies(replyTo: ActorRef[Cookies]) extends Command
case class Cookies(count: Int)
case class GiveMeCookies(count: Int, replyTo: ActorRef[Reply]) extends Command
sealed trait Reply
case class Cookies(count: Int) extends Reply
case class InvalidRequest(reason: String) extends Reply
def apply(): Behaviors.Receive[CookieFabric.GiveMeCookies] =
Behaviors.receiveMessage { message =>
message.replyTo ! Cookies(5)
if (message.count >= 5)
message.replyTo ! InvalidRequest("Too many cookies.")
else
message.replyTo ! Cookies(message.count)
Behaviors.same
}
}
@ -414,18 +420,34 @@ class InteractionPatternsSpec extends ScalaTestWithActorTestKit with WordSpecLik
// the ask is failed with a TimeoutException
implicit val timeout: Timeout = 3.seconds
val result: Future[CookieFabric.Cookies] = cookieFabric.ask(ref => CookieFabric.GiveMeCookies(ref))
val result: Future[CookieFabric.Reply] = cookieFabric.ask(ref => CookieFabric.GiveMeCookies(3, ref))
// the response callback will be executed on this execution context
implicit val ec = system.executionContext
result.onComplete {
case Success(cookies) => println(s"Yay, cookies! $cookies")
case Failure(ex) => println(s"Boo! didn't get cookies: ${ex.getMessage}")
case Success(CookieFabric.Cookies(count)) => println(s"Yay, $count cookies!")
case Success(CookieFabric.InvalidRequest(reason)) => println(s"No cookies for me. $reason")
case Failure(ex) => println(s"Boo! didn't get cookies: ${ex.getMessage}")
}
// #standalone-ask
result.futureValue shouldEqual CookieFabric.Cookies(5)
result.futureValue shouldEqual CookieFabric.Cookies(3)
// #standalone-ask-fail-future
val cookies: Future[CookieFabric.Cookies] =
cookieFabric.ask[CookieFabric.Reply](ref => CookieFabric.GiveMeCookies(3, ref)).flatMap {
case c: CookieFabric.Cookies => Future.successful(c)
case CookieFabric.InvalidRequest(reason) => Future.failed(new IllegalArgumentException(reason))
}
cookies.onComplete {
case Success(CookieFabric.Cookies(count)) => println(s"Yay, $count cookies!")
case Failure(ex) => println(s"Boo! didn't get cookies: ${ex.getMessage}")
}
// #standalone-ask-fail-future
cookies.futureValue shouldEqual CookieFabric.Cookies(3)
}
"contain a sample for pipeToSelf" in {

View file

@ -206,6 +206,17 @@ Scala
Java
: @@snip [InteractionPatternsTest.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/InteractionPatternsTest.java) { #standalone-ask }
Note that validation errors are also explicit in the message protocol. The `GiveMeCookies` request can reply
with `Cookies` or `InvalidRequest`. The requestor has to decide how to handle `InvalidRequest` reply. Sometimes
that should be treated as a failed @scala[`Future`]@java[`Future`] and for that the reply can be mapped on the
requestor side.
Scala
: @@snip [InteractionPatternsSpec.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/InteractionPatternsSpec.scala) { #standalone-ask-fail-future }
Java
: @@snip [InteractionPatternsTest.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/InteractionPatternsTest.java) { #standalone-ask-fail-future }
**Useful when:**
* Querying an actor from outside of the actor system