Ok/error protocol and failable ask #29186 (#29190)

New type StatusReply simplifies the very common use case of replying to a request with either a successful reply or an error reply which can be repetitive to define for every actor, with the additional overhead of having to make sure each such sealed top type + 2 concrete reply classes has working serialization.
This commit is contained in:
Johan Andrén 2020-07-09 16:57:53 +02:00 committed by GitHub
parent ec08c9dde4
commit 996f424835
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 2093 additions and 324 deletions

View file

@ -160,7 +160,9 @@ The adapter function is running in the receiving actor and can safely access its
In an interaction where there is a 1:1 mapping between a request and a response we can use `ask` on the `ActorContext` to interact with another actor.
The interaction has two steps, first we need to construct the outgoing message, to do that we need an @scala[`ActorRef[Response]`]@java[`ActorRef<Response>`] to put as recipient in the outgoing message. The second step is to transform the successful `Response` or failure into a message that is part of the protocol of the sending actor.
The interaction has two steps, first we need to construct the outgoing message, to do that we need an @scala[`ActorRef[Response]`]@java[`ActorRef<Response>`] to put as recipient in the outgoing message.
The second step is to transform the successful `Response` or failure into a message that is part of the protocol of the sending actor.
See also the [Generic response wrapper](#generic-response-wrapper) for replies that are either a success or an error.
**Example:**
@ -212,7 +214,7 @@ Java
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 an `InvalidRequest` reply. Sometimes
it should be treated as a failed @scala[`Future`]@java[`Future`] and for that the reply can be mapped on the
requestor side.
requestor side. See also the [Generic response wrapper](#generic-response-wrapper) for replies that are either a success or an error.
Scala
: @@snip [InteractionPatternsSpec.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/InteractionPatternsSpec.scala) { #standalone-ask-fail-future }
@ -230,6 +232,50 @@ Java
* There can only be a single response to one `ask` (see @ref:[per session child Actor](#per-session-child-actor))
* When `ask` times out, the receiving actor does not know and may still process it to completion, or even start processing it after the fact
## Generic response wrapper
In many cases the response can either be a successful result or an error (a validation error that the command was invalid for example).
Having to define two response classes and a shared supertype for every request type can be repetitive, especially in a cluster context
where you also have to make sure the messages can be serialized to be sent over the network.
To help with this a generic status-response type is included in Akka: @apidoc[StatusReply], everywhere where `ask` can be used
there is also a second method `askWithStatus` which, given that the response is a `StatusReply` will unwrap successful responses
and help with handling validation errors. Akka includes pre-built serializers for the type, so in the normal use case a clustered
application only needs to provide a serializer for the successful result.
For the case where the successful reply does not contain an actual value but is more of an acknowledgment there is a pre defined
@scala[`StatusReply.Ack`]@java[`StatusReply.ack()`] of type @scala[`StatusReply[Done]`]@java[`StatusReply<Done>`].
Errors are preferably sent as a text describing what is wrong, but using exceptions to attach a type is also possible.
**Example actor to actor ask:**
Scala
: @@snip [InteractionPatternsSpec.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/InteractionPatternsSpec.scala) { #actor-ask-with-status }
Java
: @@snip [InteractionPatternsTest.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/InteractionPatternsAskWithStatusTest.java) { #actor-ask-with-status }
A validation error is turned into a `Failure` for the message adapter. In this case we are explicitly handling the valdation error separately from
other ask failures.
**Example ask from the outside:**
Scala
: @@snip [InteractionPatternsSpec.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/InteractionPatternsSpec.scala) { #standalone-ask-with-status }
Java
: @@snip [InteractionPatternsTest.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/InteractionPatternsAskWithStatusTest.java) { #standalone-ask-with-status }
Note that validation errors are also explicit in the message protocol, but encoded as the wrapper type, constructed using @scala[`StatusReply.Error(text)`]@java[`StatusReply.error(text)`]:
Scala
: @@snip [InteractionPatternsSpec.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/InteractionPatternsSpec.scala) { #standalone-ask-with-status-fail-future }
Java
: @@snip [InteractionPatternsTest.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/InteractionPatternsAskWithStatusTest.java) { #standalone-ask-with-status-fail-future }
## Ignoring replies
In some situations an actor has a response for a particular request message but you are not interested in the response. In this case you can pass @scala[`system.ignoreRef`]@java[`system.ignoreRef()`] turning the request-response into a fire-and-forget.

View file

@ -386,8 +386,13 @@ The @ref:[Request-Response interaction pattern](interaction-patterns.md#request-
persistent actors, because you typically want to know if the command was rejected due to validation errors and
when accepted you want a confirmation when the events have been successfully stored.
Therefore you typically include a @scala[`ActorRef[ReplyMessageType]`]@java[`ActorRef<ReplyMessageType>`] in the
commands. After validation errors or after persisting events, using a `thenRun` side effect, the reply message can
Therefore you typically include a @scala[`ActorRef[ReplyMessageType]`]@java[`ActorRef<ReplyMessageType>`]. If the
command can either have a successful response or a validation error returned, the generic response type @scala[`StatusReply[ReplyType]]`]
@java[`StatusReply<ReplyType>`] can be used. If the successful reply does not contain a value but is more of an acknowledgement
a pre defined @scala[`StatusReply.Ack`]@java[`StatusReply.ack()`] of type @scala[`StatusReply[Done]`]@java[`StatusReply<Done>`]
can be used.
After validation errors or after persisting events, using a `thenRun` side effect, the reply message can
be sent to the `ActorRef`.
Scala

View file

@ -271,6 +271,9 @@ Scala
Java
: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #message-protocol }
Note that the response message hierarchy in this case could be completely avoided by using the @apiDoc[StatusReply] API
instead (see @ref[Generic Response Wrapper](interaction-patterns.md#generic-response-wrapper)).
## Public versus private messages
Often an actor has some messages that are only for its internal implementation and not part of the public