Reword the "private messages" typed style docs (#27392)
Use Message/Command/Event as a better example, as the PrivateCommand/Command example seemed to go out of its way to look like an unattractive implementation strategy. Oh, and touch up the "Partial versus total Function" section below too.
This commit is contained in:
parent
56f3a6b126
commit
c4417d1c59
3 changed files with 44 additions and 35 deletions
|
|
@ -646,11 +646,13 @@ interface StyleGuideDocExamples {
|
|||
interface PublicVsPrivateMessages2 {
|
||||
// #public-private-messages-2
|
||||
// above example is preferred, but this is possible and not wrong
|
||||
public class Counter extends AbstractBehavior<Counter.PrivateCommand> {
|
||||
public class Counter extends AbstractBehavior<Counter.Message> {
|
||||
|
||||
public interface PrivateCommand {}
|
||||
// The type of all public and private messages the Counter actor handles
|
||||
public interface Message {}
|
||||
|
||||
public interface Command extends PrivateCommand {}
|
||||
/** Counter's public message protocol type. */
|
||||
public interface Command extends Message {}
|
||||
|
||||
public enum Increment implements Command {
|
||||
INSTANCE
|
||||
|
|
@ -672,14 +674,17 @@ interface StyleGuideDocExamples {
|
|||
}
|
||||
}
|
||||
|
||||
// Tick is a PrivateCommand so can't be sent to an ActorRef<Command>
|
||||
enum Tick implements PrivateCommand {
|
||||
// The type of the Counter actor's internal event messages.
|
||||
private interface Event extends Message {}
|
||||
|
||||
// Tick is a private Event so can't be sent to an ActorRef<Command>
|
||||
private enum Tick implements Event {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
public static Behavior<Command> create(String name, Duration tickInterval) {
|
||||
return Behaviors.setup(
|
||||
(ActorContext<PrivateCommand> context) ->
|
||||
(ActorContext<Message> context) ->
|
||||
Behaviors.withTimers(
|
||||
timers -> {
|
||||
timers.startTimerWithFixedDelay("tick", Tick.INSTANCE, tickInterval);
|
||||
|
|
@ -689,16 +694,16 @@ interface StyleGuideDocExamples {
|
|||
}
|
||||
|
||||
private final String name;
|
||||
private final ActorContext<PrivateCommand> context;
|
||||
private final ActorContext<Message> context;
|
||||
private int count;
|
||||
|
||||
private Counter(String name, ActorContext<PrivateCommand> context) {
|
||||
private Counter(String name, ActorContext<Message> context) {
|
||||
this.name = name;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<PrivateCommand> createReceive() {
|
||||
public Receive<Message> createReceive() {
|
||||
return newReceiveBuilder()
|
||||
.onMessage(Increment.class, notUsed -> onIncrement())
|
||||
.onMessage(Tick.class, notUsed -> onTick())
|
||||
|
|
@ -706,19 +711,19 @@ interface StyleGuideDocExamples {
|
|||
.build();
|
||||
}
|
||||
|
||||
private Behavior<PrivateCommand> onIncrement() {
|
||||
private Behavior<Message> onIncrement() {
|
||||
count++;
|
||||
context.getLog().debug("[{}] Incremented counter to [{}]", name, count);
|
||||
return this;
|
||||
}
|
||||
|
||||
private Behavior<PrivateCommand> onTick() {
|
||||
private Behavior<Message> onTick() {
|
||||
count++;
|
||||
context.getLog().debug("[{}] Incremented counter by background tick to [{}]", name, count);
|
||||
return this;
|
||||
}
|
||||
|
||||
private Behavior<PrivateCommand> onGetValue(GetValue command) {
|
||||
private Behavior<Message> onGetValue(GetValue command) {
|
||||
command.replyTo.tell(new Value(count));
|
||||
return this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -371,18 +371,23 @@ object StyleGuideDocExamples {
|
|||
//#public-private-messages-2
|
||||
// above example is preferred, but this is possible and not wrong
|
||||
object Counter {
|
||||
sealed trait PrivateCommand
|
||||
sealed trait Command extends PrivateCommand
|
||||
// The type of all public and private messages the Counter actor handles
|
||||
sealed trait Message
|
||||
|
||||
/** Counter's public message protocol type. */
|
||||
sealed trait Command extends Message
|
||||
case object Increment extends Command
|
||||
final case class GetValue(replyTo: ActorRef[Value]) extends Command
|
||||
final case class Value(n: Int)
|
||||
|
||||
// Tick is a PrivateCommand so can't be sent to an ActorRef[Command]
|
||||
case object Tick extends PrivateCommand
|
||||
// The type of the Counter actor's internal event messages.
|
||||
private sealed trait Event extends Message
|
||||
// Tick is a private Event so can't be sent to an ActorRef[Command]
|
||||
private case object Tick extends Event
|
||||
|
||||
def apply(name: String, tickInterval: FiniteDuration): Behavior[Command] = {
|
||||
Behaviors
|
||||
.setup[Counter.PrivateCommand] { context =>
|
||||
.setup[Counter.Message] { context =>
|
||||
Behaviors.withTimers { timers =>
|
||||
timers.startTimerWithFixedDelay("tick", Tick, tickInterval)
|
||||
new Counter(name, context).counter(0)
|
||||
|
|
@ -392,10 +397,10 @@ object StyleGuideDocExamples {
|
|||
}
|
||||
}
|
||||
|
||||
class Counter private (name: String, context: ActorContext[Counter.PrivateCommand]) {
|
||||
class Counter private (name: String, context: ActorContext[Counter.Message]) {
|
||||
import Counter._
|
||||
|
||||
private def counter(n: Int): Behavior[PrivateCommand] =
|
||||
private def counter(n: Int): Behavior[Message] =
|
||||
Behaviors.receiveMessage {
|
||||
case Increment =>
|
||||
val newValue = n + 1
|
||||
|
|
|
|||
|
|
@ -272,14 +272,14 @@ Java
|
|||
|
||||
## Public versus private messages
|
||||
|
||||
Often an actor has some messages that are only for it's internal implementation and not part of the public
|
||||
message protocol. For example, it can be timer messages or wrapper messages for `ask` or `messageAdapter`.
|
||||
Often an actor has some messages that are only for its internal implementation and not part of the public
|
||||
message protocol, such as timer messages or wrapper messages for `ask` or `messageAdapter`.
|
||||
|
||||
That can be be achieved by defining those messages with `private` visibility. Then they can't be accessed
|
||||
and sent from the outside of the actor. The private messages must still @scala[extend]@java[implement] the
|
||||
Such messages should be declared `private` so they can't be accessed
|
||||
and sent from the outside of the actor. Note that they must still @scala[extend]@java[implement] the
|
||||
public `Command` @scala[trait]@java[interface].
|
||||
|
||||
Example of a private visibility for internal message:
|
||||
Here is an example of using `private` for an internal message:
|
||||
|
||||
Scala
|
||||
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #public-private-messages-1 }
|
||||
|
|
@ -287,12 +287,12 @@ Scala
|
|||
Java
|
||||
: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #public-private-messages-1 }
|
||||
|
||||
There is another approach, which is valid but more complicated. It's not relying on visibility from the programming
|
||||
language but instead only exposing part of the message class hierarchy to the outside, by using `narrow`. The
|
||||
former approach is recommended but it can be good to know this "trick", for example it can be useful when
|
||||
An alternative approach is using a type hierarchy and `narrow` to have a super-type for the public messages as a
|
||||
distinct type from the super-type of all actor messages. The
|
||||
former approach is recommended but it is good to know this alternative as it can be useful when
|
||||
using shared message protocol classes as described in @ref:[Where to define messages](#where-to-define-messages).
|
||||
|
||||
Example of not exposing internal message in public `Behavior` type:
|
||||
Here's an example of using a type hierarchy to separate public and private messages:
|
||||
|
||||
Scala
|
||||
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #public-private-messages-2 }
|
||||
|
|
@ -359,16 +359,15 @@ Also, don't use braces and return statements in one-line lambda bodies.
|
|||
|
||||
## Partial versus total Function
|
||||
|
||||
It's recommended to use a `sealed` trait as the super type of the commands (incoming messages) of a an actor
|
||||
because then the Scala compiler will emit a warning if a message type is forgotten in the pattern match.
|
||||
It's recommended to use a `sealed` trait as the super type of the commands (incoming messages) of an actor
|
||||
as the compiler will emit a warning if a message type is forgotten in the pattern match.
|
||||
|
||||
Scala
|
||||
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #messages-sealed }
|
||||
|
||||
That is the main reason for why `Behaviors.receive`, `Behaviors.receiveMessage` takes a total `Function` and
|
||||
not a `PartialFunction`.
|
||||
That is the main reason for `Behaviors.receive`, `Behaviors.receiveMessage` taking a `Function` rather than a `PartialFunction`.
|
||||
|
||||
The compiler warning if `GetValue` is not handled:
|
||||
The compiler warning if `GetValue` is not handled would be:
|
||||
|
||||
```
|
||||
[warn] ... Counter.scala:45:34: match may not be exhaustive.
|
||||
|
|
@ -384,13 +383,13 @@ in the pattern match and return `Behaviors.unhandled`.
|
|||
Scala
|
||||
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #pattern-match-unhandled }
|
||||
|
||||
One thing to be aware of is the exhaustiveness check is not enabled when there is a guard condition in the
|
||||
One thing to be aware of is the exhaustiveness check is not enabled when there is a guard condition in any of the
|
||||
pattern match cases.
|
||||
|
||||
Scala
|
||||
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #pattern-match-guard }
|
||||
|
||||
Therefore it can be better to not use the guard and instead move the `if` after the `=>`.
|
||||
Therefore, for the purposes of exhaustivity checking, it is be better to not use guards and instead move the `if`s after the `=>`.
|
||||
|
||||
Scala
|
||||
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #pattern-match-without-guard }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue