diff --git a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java index 570e913b8c..e853e64f10 100644 --- a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java +++ b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java @@ -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 { + public class Counter extends AbstractBehavior { - 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 - 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 + private enum Tick implements Event { INSTANCE } public static Behavior create(String name, Duration tickInterval) { return Behaviors.setup( - (ActorContext context) -> + (ActorContext context) -> Behaviors.withTimers( timers -> { timers.startTimerWithFixedDelay("tick", Tick.INSTANCE, tickInterval); @@ -689,16 +694,16 @@ interface StyleGuideDocExamples { } private final String name; - private final ActorContext context; + private final ActorContext context; private int count; - private Counter(String name, ActorContext context) { + private Counter(String name, ActorContext context) { this.name = name; this.context = context; } @Override - public Receive createReceive() { + public Receive createReceive() { return newReceiveBuilder() .onMessage(Increment.class, notUsed -> onIncrement()) .onMessage(Tick.class, notUsed -> onTick()) @@ -706,19 +711,19 @@ interface StyleGuideDocExamples { .build(); } - private Behavior onIncrement() { + private Behavior onIncrement() { count++; context.getLog().debug("[{}] Incremented counter to [{}]", name, count); return this; } - private Behavior onTick() { + private Behavior onTick() { count++; context.getLog().debug("[{}] Incremented counter by background tick to [{}]", name, count); return this; } - private Behavior onGetValue(GetValue command) { + private Behavior onGetValue(GetValue command) { command.replyTo.tell(new Value(count)); return this; } diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala index 27d580640c..4de1c2e7d8 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala @@ -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 diff --git a/akka-docs/src/main/paradox/typed/style-guide.md b/akka-docs/src/main/paradox/typed/style-guide.md index 2297849cd9..f6236c0792 100644 --- a/akka-docs/src/main/paradox/typed/style-guide.md +++ b/akka-docs/src/main/paradox/typed/style-guide.md @@ -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 }