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:
Dale Wijnand 2019-07-29 11:00:27 +01:00 committed by Arnout Engelen
parent 56f3a6b126
commit c4417d1c59
3 changed files with 44 additions and 35 deletions

View file

@ -646,11 +646,13 @@ interface StyleGuideDocExamples {
interface PublicVsPrivateMessages2 { interface PublicVsPrivateMessages2 {
// #public-private-messages-2 // #public-private-messages-2
// above example is preferred, but this is possible and not wrong // 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 { public enum Increment implements Command {
INSTANCE INSTANCE
@ -672,14 +674,17 @@ interface StyleGuideDocExamples {
} }
} }
// Tick is a PrivateCommand so can't be sent to an ActorRef<Command> // The type of the Counter actor's internal event messages.
enum Tick implements PrivateCommand { 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 INSTANCE
} }
public static Behavior<Command> create(String name, Duration tickInterval) { public static Behavior<Command> create(String name, Duration tickInterval) {
return Behaviors.setup( return Behaviors.setup(
(ActorContext<PrivateCommand> context) -> (ActorContext<Message> context) ->
Behaviors.withTimers( Behaviors.withTimers(
timers -> { timers -> {
timers.startTimerWithFixedDelay("tick", Tick.INSTANCE, tickInterval); timers.startTimerWithFixedDelay("tick", Tick.INSTANCE, tickInterval);
@ -689,16 +694,16 @@ interface StyleGuideDocExamples {
} }
private final String name; private final String name;
private final ActorContext<PrivateCommand> context; private final ActorContext<Message> context;
private int count; private int count;
private Counter(String name, ActorContext<PrivateCommand> context) { private Counter(String name, ActorContext<Message> context) {
this.name = name; this.name = name;
this.context = context; this.context = context;
} }
@Override @Override
public Receive<PrivateCommand> createReceive() { public Receive<Message> createReceive() {
return newReceiveBuilder() return newReceiveBuilder()
.onMessage(Increment.class, notUsed -> onIncrement()) .onMessage(Increment.class, notUsed -> onIncrement())
.onMessage(Tick.class, notUsed -> onTick()) .onMessage(Tick.class, notUsed -> onTick())
@ -706,19 +711,19 @@ interface StyleGuideDocExamples {
.build(); .build();
} }
private Behavior<PrivateCommand> onIncrement() { private Behavior<Message> onIncrement() {
count++; count++;
context.getLog().debug("[{}] Incremented counter to [{}]", name, count); context.getLog().debug("[{}] Incremented counter to [{}]", name, count);
return this; return this;
} }
private Behavior<PrivateCommand> onTick() { private Behavior<Message> onTick() {
count++; count++;
context.getLog().debug("[{}] Incremented counter by background tick to [{}]", name, count); context.getLog().debug("[{}] Incremented counter by background tick to [{}]", name, count);
return this; return this;
} }
private Behavior<PrivateCommand> onGetValue(GetValue command) { private Behavior<Message> onGetValue(GetValue command) {
command.replyTo.tell(new Value(count)); command.replyTo.tell(new Value(count));
return this; return this;
} }

View file

@ -371,18 +371,23 @@ object StyleGuideDocExamples {
//#public-private-messages-2 //#public-private-messages-2
// above example is preferred, but this is possible and not wrong // above example is preferred, but this is possible and not wrong
object Counter { object Counter {
sealed trait PrivateCommand // The type of all public and private messages the Counter actor handles
sealed trait Command extends PrivateCommand sealed trait Message
/** Counter's public message protocol type. */
sealed trait Command extends Message
case object Increment extends Command case object Increment extends Command
final case class GetValue(replyTo: ActorRef[Value]) extends Command final case class GetValue(replyTo: ActorRef[Value]) extends Command
final case class Value(n: Int) final case class Value(n: Int)
// Tick is a PrivateCommand so can't be sent to an ActorRef[Command] // The type of the Counter actor's internal event messages.
case object Tick extends PrivateCommand 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] = { def apply(name: String, tickInterval: FiniteDuration): Behavior[Command] = {
Behaviors Behaviors
.setup[Counter.PrivateCommand] { context => .setup[Counter.Message] { context =>
Behaviors.withTimers { timers => Behaviors.withTimers { timers =>
timers.startTimerWithFixedDelay("tick", Tick, tickInterval) timers.startTimerWithFixedDelay("tick", Tick, tickInterval)
new Counter(name, context).counter(0) 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._ import Counter._
private def counter(n: Int): Behavior[PrivateCommand] = private def counter(n: Int): Behavior[Message] =
Behaviors.receiveMessage { Behaviors.receiveMessage {
case Increment => case Increment =>
val newValue = n + 1 val newValue = n + 1

View file

@ -272,14 +272,14 @@ Java
## Public versus private messages ## Public versus private messages
Often an actor has some messages that are only for it's internal implementation and not part of the public Often an actor has some messages that are only for its internal implementation and not part of the public
message protocol. For example, it can be timer messages or wrapper messages for `ask` or `messageAdapter`. 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 Such messages should be declared `private` so they can't be accessed
and sent from the outside of the actor. The private messages must still @scala[extend]@java[implement] the and sent from the outside of the actor. Note that they must still @scala[extend]@java[implement] the
public `Command` @scala[trait]@java[interface]. 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 Scala
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #public-private-messages-1 } : @@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 Java
: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #public-private-messages-1 } : @@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 An alternative approach is using a type hierarchy and `narrow` to have a super-type for the public messages as a
language but instead only exposing part of the message class hierarchy to the outside, by using `narrow`. The distinct type from the super-type of all actor messages. The
former approach is recommended but it can be good to know this "trick", for example it can be useful when 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). 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 Scala
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #public-private-messages-2 } : @@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 ## 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 It's recommended to use a `sealed` trait as the super type of the commands (incoming messages) of an actor
because then the Scala compiler will emit a warning if a message type is forgotten in the pattern match. as the compiler will emit a warning if a message type is forgotten in the pattern match.
Scala Scala
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #messages-sealed } : @@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 That is the main reason for `Behaviors.receive`, `Behaviors.receiveMessage` taking a `Function` rather than a `PartialFunction`.
not 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. [warn] ... Counter.scala:45:34: match may not be exhaustive.
@ -384,13 +383,13 @@ in the pattern match and return `Behaviors.unhandled`.
Scala Scala
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #pattern-match-unhandled } : @@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. pattern match cases.
Scala Scala
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #pattern-match-guard } : @@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 Scala
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #pattern-match-without-guard } : @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #pattern-match-without-guard }