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:
parent
f914ab259d
commit
b863646258
3 changed files with 120 additions and 19 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue