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 0149a1c374..570e913b8c 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 @@ -6,18 +6,19 @@ package jdocs.akka.typed; // #oo-style // #fun-style -import akka.actor.typed.ActorRef; import akka.actor.typed.Behavior; import akka.actor.typed.javadsl.ActorContext; import akka.actor.typed.javadsl.Behaviors; // #fun-style import akka.actor.typed.javadsl.AbstractBehavior; import akka.actor.typed.javadsl.Receive; -import akka.actor.typed.javadsl.TimerScheduler; - -import java.time.Duration; // #oo-style +import akka.actor.typed.ActorRef; +import akka.actor.typed.javadsl.TimerScheduler; +import akka.Done; +import java.time.Duration; + interface StyleGuideDocExamples { interface FunctionalStyle { @@ -76,13 +77,17 @@ interface StyleGuideDocExamples { interface OOStyle { // #oo-style + + // #messages public class Counter extends AbstractBehavior { public interface Command {} + // #message-enum public enum Increment implements Command { INSTANCE } + // #message-enum public static class GetValue implements Command { public final ActorRef replyTo; @@ -99,6 +104,7 @@ interface StyleGuideDocExamples { this.value = value; } } + // #messages public static Behavior create() { return Behaviors.setup(Counter::new); @@ -115,7 +121,9 @@ interface StyleGuideDocExamples { @Override public Receive createReceive() { return newReceiveBuilder() + // #message-enum-match .onMessage(Increment.class, notUsed -> onIncrement()) + // #message-enum-match .onMessage(GetValue.class, this::onGetValue) .build(); } @@ -130,7 +138,9 @@ interface StyleGuideDocExamples { command.replyTo.tell(new Value(n)); return this; } + // #messages } + // #messages // #oo-style } @@ -403,4 +413,316 @@ interface StyleGuideDocExamples { } // #fun-style-setup-params3 } + + interface FactoryMethod { + // #behavior-factory-method + public class CountDown extends AbstractBehavior { + + public interface Command {} + + public enum Down implements Command { + INSTANCE + } + + // factory for the initial `Behavior` + public static Behavior create(int countDownFrom, ActorRef notifyWhenZero) { + return Behaviors.setup(context -> new CountDown(countDownFrom, notifyWhenZero)); + } + + private final ActorRef notifyWhenZero; + private int remaining; + + private CountDown(int countDownFrom, ActorRef notifyWhenZero) { + this.remaining = countDownFrom; + this.notifyWhenZero = notifyWhenZero; + } + + @Override + public Receive createReceive() { + return newReceiveBuilder().onMessage(Down.class, notUsed -> onDown()).build(); + } + + private Behavior onDown() { + remaining--; + if (remaining == 0) { + notifyWhenZero.tell(Done.getInstance()); + return Behaviors.stopped(); + } else { + return this; + } + } + } + // #behavior-factory-method + + public class Usage { + private ActorContext context = null; + private ActorRef doneRef = null; + + { + // #behavior-factory-method-spawn + ActorRef countDown = + context.spawn(CountDown.create(100, doneRef), "countDown"); + // #behavior-factory-method-spawn + + // #message-prefix-in-tell + countDown.tell(CountDown.Down.INSTANCE); + // #message-prefix-in-tell + } + } + } + + interface Messages { + // #message-protocol + interface CounterProtocol { + interface Command {} + + public static class Increment implements Command { + public final int delta; + private final ActorRef replyTo; + + public Increment(int delta, ActorRef replyTo) { + this.delta = delta; + this.replyTo = replyTo; + } + } + + public static class Decrement implements Command { + public final int delta; + private final ActorRef replyTo; + + public Decrement(int delta, ActorRef replyTo) { + this.delta = delta; + this.replyTo = replyTo; + } + } + + interface OperationResult {} + + enum Confirmed implements OperationResult { + INSTANCE + } + + public static class Rejected implements OperationResult { + public final String reason; + + public Rejected(String reason) { + this.reason = reason; + } + } + } + // #message-protocol + } + + interface PublicVsPrivateMessages1 { + // #on-message-lambda-anti + // this is an anti-pattern, don't use lambdas with a large block of code + // #on-message-lambda-anti + // #public-private-messages-1 + public class Counter extends AbstractBehavior { + + public interface Command {} + + public enum Increment implements Command { + INSTANCE + } + + public static class GetValue implements Command { + public final ActorRef replyTo; + + public GetValue(ActorRef replyTo) { + this.replyTo = replyTo; + } + } + + public static class Value { + public final int value; + + public Value(int value) { + this.value = value; + } + } + + // Tick is private so can't be sent from the outside + private enum Tick implements Command { + INSTANCE + } + + public static Behavior create(String name, Duration tickInterval) { + return Behaviors.setup( + context -> + Behaviors.withTimers( + timers -> { + timers.startTimerWithFixedDelay("tick", Tick.INSTANCE, tickInterval); + return new Counter(name, context); + })); + } + + private final String name; + private final ActorContext context; + private int count; + + private Counter(String name, ActorContext context) { + this.name = name; + this.context = context; + } + + // #on-message-lambda + // #on-message-method-ref + @Override + // #on-message-lambda-anti + public Receive createReceive() { + // #on-message-lambda-anti + return newReceiveBuilder() + // #on-message-method-ref + .onMessage(Increment.class, notUsed -> onIncrement()) + // #on-message-lambda + .onMessage(Tick.class, notUsed -> onTick()) + // #on-message-method-ref + .onMessage(GetValue.class, this::onGetValue) + // #on-message-lambda + .build(); + } + + // #on-message-lambda + // #on-message-method-ref + + // #on-message-lambda + private Behavior onIncrement() { + count++; + context.getLog().debug("[{}] Incremented counter to [{}]", name, count); + return this; + } + // #on-message-lambda + + private Behavior onTick() { + count++; + context.getLog().debug("[{}] Incremented counter by background tick to [{}]", name, count); + return this; + } + + // #on-message-method-ref + private Behavior onGetValue(GetValue command) { + command.replyTo.tell(new Value(count)); + return this; + } + // #on-message-method-ref + + // #public-private-messages-1 + // anti-pattern, don't do like this + public Receive createReceiveAnti() { + // #on-message-lambda-anti + return newReceiveBuilder() + .onMessage( + Increment.class, + notUsed -> { + count++; + context.getLog().debug("[{}] Incremented counter to [{}]", name, count); + return this; + }) + .onMessage( + Tick.class, + notUsed -> { + count++; + context + .getLog() + .debug("[{}] Incremented counter by background tick to [{}]", name, count); + return this; + }) + .onMessage( + GetValue.class, + command -> { + command.replyTo.tell(new Value(count)); + return this; + }) + .build(); + } + // #on-message-lambda-anti + // #public-private-messages-1 + } + // #public-private-messages-1 + + } + + interface PublicVsPrivateMessages2 { + // #public-private-messages-2 + // above example is preferred, but this is possible and not wrong + public class Counter extends AbstractBehavior { + + public interface PrivateCommand {} + + public interface Command extends PrivateCommand {} + + public enum Increment implements Command { + INSTANCE + } + + public static class GetValue implements Command { + public final ActorRef replyTo; + + public GetValue(ActorRef replyTo) { + this.replyTo = replyTo; + } + } + + public static class Value { + public final int value; + + public Value(int value) { + this.value = value; + } + } + + // Tick is a PrivateCommand so can't be sent to an ActorRef + enum Tick implements PrivateCommand { + INSTANCE + } + + public static Behavior create(String name, Duration tickInterval) { + return Behaviors.setup( + (ActorContext context) -> + Behaviors.withTimers( + timers -> { + timers.startTimerWithFixedDelay("tick", Tick.INSTANCE, tickInterval); + return new Counter(name, context); + })) + .narrow(); // note narrow here + } + + private final String name; + private final ActorContext context; + private int count; + + private Counter(String name, ActorContext context) { + this.name = name; + this.context = context; + } + + @Override + public Receive createReceive() { + return newReceiveBuilder() + .onMessage(Increment.class, notUsed -> onIncrement()) + .onMessage(Tick.class, notUsed -> onTick()) + .onMessage(GetValue.class, this::onGetValue) + .build(); + } + + private Behavior onIncrement() { + count++; + context.getLog().debug("[{}] Incremented counter to [{}]", name, count); + return this; + } + + private Behavior onTick() { + count++; + context.getLog().debug("[{}] Incremented counter by background tick to [{}]", name, count); + return this; + } + + private Behavior onGetValue(GetValue command) { + command.replyTo.tell(new Value(count)); + return this; + } + } + // #public-private-messages-2 + } } 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 45d2df39cc..27d580640c 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 @@ -4,15 +4,23 @@ package docs.akka.typed -//#oo-style -//#fun-style +import scala.concurrent.duration._ +import scala.concurrent.Future + +import akka.actor.typed.ActorSystem +import akka.actor.typed.ActorRef +import akka.actor.typed.scaladsl.TimerScheduler import scala.concurrent.duration.FiniteDuration -import akka.actor.typed.ActorRef +import akka.Done +import com.github.ghik.silencer.silent + +//#oo-style +//#fun-style + import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors -import akka.actor.typed.scaladsl.TimerScheduler //#fun-style import akka.actor.typed.scaladsl.AbstractBehavior //#oo-style @@ -23,11 +31,13 @@ object StyleGuideDocExamples { //#fun-style + //#messages object Counter { sealed trait Command case object Increment extends Command final case class GetValue(replyTo: ActorRef[Value]) extends Command final case class Value(n: Int) + //#messages def apply(): Behavior[Command] = counter(0) @@ -44,7 +54,9 @@ object StyleGuideDocExamples { Behaviors.same } } + //#messages } + //#messages //#fun-style } @@ -52,6 +64,7 @@ object StyleGuideDocExamples { object OOStyle { //#oo-style + object Counter { sealed trait Command case object Increment extends Command @@ -254,4 +267,253 @@ object StyleGuideDocExamples { // #fun-style-setup-params4 } + object FactoryMethod { + //#behavior-factory-method + object CountDown { + sealed trait Command + case object Down extends Command + + // factory for the initial `Behavior` + def apply(countDownFrom: Int, notifyWhenZero: ActorRef[Done]): Behavior[Command] = + new CountDown(notifyWhenZero).counter(countDownFrom) + } + + private class CountDown(notifyWhenZero: ActorRef[Done]) { + import CountDown._ + + private def counter(remaining: Int): Behavior[Command] = { + //#exhastivness-check + Behaviors.receiveMessage { + case Down => + if (remaining == 1) { + notifyWhenZero.tell(Done) + Behaviors.stopped + } else + counter(remaining - 1) + } + //#exhastivness-check + } + + } + //#behavior-factory-method + + object Usage { + val context: ActorContext[_] = ??? + val doneRef: ActorRef[Done] = ??? + + //#behavior-factory-method-spawn + val countDown = context.spawn(CountDown(100, doneRef), "countDown") + //#behavior-factory-method-spawn + + //#message-prefix-in-tell + countDown ! CountDown.Down + //#message-prefix-in-tell + } + } + + object Messages { + //#message-protocol + object CounterProtocol { + sealed trait Command + + final case class Increment(delta: Int, replyTo: ActorRef[OperationResult]) extends Command + final case class Decrement(delta: Int, replyTo: ActorRef[OperationResult]) extends Command + + sealed trait OperationResult + case object Confirmed extends OperationResult + final case class Rejected(reason: String) + } + //#message-protocol + } + + object PublicVsPrivateMessages1 { + //#public-private-messages-1 + object Counter { + sealed trait Command + case object Increment extends Command + final case class GetValue(replyTo: ActorRef[Value]) extends Command + final case class Value(n: Int) + + // Tick is private so can't be sent from the outside + private case object Tick extends Command + + def apply(name: String, tickInterval: FiniteDuration): Behavior[Command] = + Behaviors.setup { context => + Behaviors.withTimers { timers => + timers.startTimerWithFixedDelay("tick", Tick, tickInterval) + new Counter(name, context).counter(0) + } + } + } + + class Counter private (name: String, context: ActorContext[Counter.Command]) { + import Counter._ + + private def counter(n: Int): Behavior[Command] = + Behaviors.receiveMessage { + case Increment => + val newValue = n + 1 + context.log.debug("[{}] Incremented counter to [{}]", name, newValue) + counter(newValue) + case Tick => + val newValue = n + 1 + context.log.debug("[{}] Incremented counter by background tick to [{}]", name, newValue) + counter(newValue) + case GetValue(replyTo) => + replyTo ! Value(n) + Behaviors.same + } + } + //#public-private-messages-1 + } + + object PublicVsPrivateMessages2 { + //#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 + 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 + + def apply(name: String, tickInterval: FiniteDuration): Behavior[Command] = { + Behaviors + .setup[Counter.PrivateCommand] { context => + Behaviors.withTimers { timers => + timers.startTimerWithFixedDelay("tick", Tick, tickInterval) + new Counter(name, context).counter(0) + } + } + .narrow // note narrow here + } + } + + class Counter private (name: String, context: ActorContext[Counter.PrivateCommand]) { + import Counter._ + + private def counter(n: Int): Behavior[PrivateCommand] = + Behaviors.receiveMessage { + case Increment => + val newValue = n + 1 + context.log.debug("[{}] Incremented counter to [{}]", name, newValue) + counter(newValue) + case Tick => + val newValue = n + 1 + context.log.debug("[{}] Incremented counter by background tick to [{}]", name, newValue) + counter(newValue) + case GetValue(replyTo) => + replyTo ! Value(n) + Behaviors.same + } + } + //#public-private-messages-2 + } + + object Ask { + import Messages.CounterProtocol._ + + val system: ActorSystem[Nothing] = ??? + + //#ask-1 + import akka.actor.typed.scaladsl.AskPattern._ + import akka.util.Timeout + + implicit val timeout = Timeout(3.seconds) + implicit val scheduler = system.scheduler + val counter: ActorRef[Command] = ??? + + val result: Future[OperationResult] = counter.ask(replyTo => Increment(delta = 2, replyTo)) + //#ask-1 + + //#ask-2 + val result2: Future[OperationResult] = counter.ask(Increment(delta = 2, _)) + //#ask-2 + + /* + //#ask-3 + // doesn't compile + val result3: Future[OperationResult] = counter ? Increment(delta = 2, _) + //#ask-3 + */ + + //#ask-4 + val result3: Future[OperationResult] = counter ? (Increment(delta = 2, _)) + //#ask-4 + } + + object ExhaustivenessCheck { + + object CountDown { + //#messages-sealed + sealed trait Command + case object Down extends Command + final case class GetValue(replyTo: ActorRef[Value]) extends Command + final case class Value(n: Int) + //#messages-sealed + + def apply(countDownFrom: Int, notifyWhenZero: ActorRef[Done]): Behavior[Command] = + new CountDown(notifyWhenZero).counterWithGuard(countDownFrom) + } + + private class CountDown(notifyWhenZero: ActorRef[Done]) { + import CountDown._ + + private def counterWithGuard(remaining: Int): Behavior[Command] = { + //#pattern-match-guard + // no exhaustiveness check because of guard condition + Behaviors.receiveMessage { + case Down if remaining == 1 => + notifyWhenZero.tell(Done) + zero + case Down => + counter(remaining - 1) + } + //#pattern-match-guard + } + + @silent + private def counter(remaining: Int): Behavior[Command] = { + //#pattern-match-without-guard + Behaviors.receiveMessage { + case Down => + if (remaining == 1) { + notifyWhenZero.tell(Done) + zero + } else + counter(remaining - 1) + } + //#pattern-match-without-guard + } + + //#pattern-match-unhandled + private val zero: Behavior[Command] = { + Behaviors.receiveMessage { + case GetValue(replyTo) => + replyTo ! Value(0) + Behaviors.same + case Down => + Behaviors.unhandled + } + } + //#pattern-match-unhandled + + @silent + object partial { + //#pattern-match-partial + private val zero: Behavior[Command] = { + Behaviors.receiveMessagePartial { + case GetValue(replyTo) => + replyTo ! Value(0) + Behaviors.same + } + } + //#pattern-match-partial + } + + } + } } diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/AbstractBehavior.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/AbstractBehavior.scala index 4e67e779b3..4ee7b6fccc 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/AbstractBehavior.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/AbstractBehavior.scala @@ -18,7 +18,8 @@ import akka.util.OptionVal * * Instances of this behavior should be created via [[Behaviors.setup]] and if * the [[ActorContext]] is needed it can be passed as a constructor parameter - * from the factory function. + * from the factory function. This is important because a new instance + * should be created when restart supervision is used. * * @see [[Behaviors.setup]] */ diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/AbstractBehavior.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/AbstractBehavior.scala index 62fc8e2470..a8ad121de3 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/AbstractBehavior.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/AbstractBehavior.scala @@ -18,7 +18,8 @@ import akka.actor.typed.{ Behavior, ExtensibleBehavior, Signal, TypedActorContex * * Instances of this behavior should be created via [[Behaviors.setup]] and if * the [[ActorContext]] is needed it can be passed as a constructor parameter - * from the factory function. + * from the factory function. This is important because a new instance + * should be created when restart supervision is used. * * @see [[Behaviors.setup]] */ diff --git a/akka-docs/src/main/paradox/typed/style-guide.md b/akka-docs/src/main/paradox/typed/style-guide.md index 7286526f58..8e2a20e575 100644 --- a/akka-docs/src/main/paradox/typed/style-guide.md +++ b/akka-docs/src/main/paradox/typed/style-guide.md @@ -1,6 +1,11 @@ # Style guide -## Functional vs object-oriented style +This is a style guide with recommendations of idioms and pattern for writing Akka Typed actors. + +As with all style guides, treat this as a list of rules to be broken. There are certainly times +when alternative styles should be preferred over the ones given here. + +## Functional versus object-oriented style There are two flavors of the Actor APIs. @@ -51,7 +56,7 @@ A few differences to note: the message. That said, `Behaviors.setup` is often used in the functional style as well, and then often together with `Behaviors.receiveMessage` that doesn't pass in the context with the message.] @java[The `ActorContext` is accessed with `Behaviors.setup` but then kept in different ways. - As an instance field vs. a method parameter.] + As an instance field versus a method parameter.] Which style you choose to use is a matter of taste and both styles can be mixed depending on which is best for a specific actor. An actor can switch between behaviors implemented in different styles. @@ -169,7 +174,7 @@ That's nice. One thing to be cautious with here is that it's important that you each spawned actor, since those parameters must not be shared between different actor instances. That comes natural when creating the instance from `Behaviors.setup` as in the above example. Having a @scala[`apply` factory method in the companion object and making the constructor private is recommended.] -@java[static `create` factory method and making the constructor private is highly recommended.] +@java[static `create` factory method and making the constructor private is recommended.] This can also be useful when testing the behavior by creating a test subclass that overrides certain methods in the class. The test would create the instance without the @scala[`apply` factory method]@java[static `create` factory method]. @@ -190,3 +195,256 @@ Scala : @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #fun-style-setup-params4 } @@@ + +## Behavior factory method + +The initial behavior should be created via @scala[a factory method in the companion object]@java[a static factory method]. +Thereby the usage of the behavior doesn't change when the implementation is changed, for example if +changing between object-oriented and function style. + +The factory method is a good place for retrieving resources like `Behaviors.withTimers`, `Behaviors.withStash` +and `ActorContext` with `Behaviors.setup`. + +When using the object-oriented style, `AbstractBehavior`, a new instance should be created from a `Behaviors.setup` +block in this factory method even though the `ActorContext` is not needed. This is important because a new +instance should be created when restart supervision is used. Typically, the `ActorContext` is needed anyway. + +The naming convention for the factory method is @scala[`apply` (when using Scala)]@java[`create` (when using Java)]. +Consistent naming makes it easier for readers of the code to find the "starting point" of the behavior. + +In the functional style the factory could even have been defined as a @scala[`val`]@java[`static field`] +if all state is immutable and captured by the function, but since most behaviors need some initialization +parameters it is preferred to consistently use a method @scala[(`def`)] for the factory. + +Example: + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #behavior-factory-method } + +Java +: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #behavior-factory-method } + +When spawning an actor from this initial behavior it looks like: + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #behavior-factory-method-spawn } + +Java +: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #behavior-factory-method-spawn } + + +## Where to define messages + +When sending messages to another actor or receiving responses the messages should be prefixed with the name +of the actor/behavior that defines the message to make it clear and avoid ambiguity. + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #message-prefix-in-tell } + +Java +: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #message-prefix-in-tell } + +That is preferred over using @scala[importing `Down` and using `countDown ! Down`] +@java[importing `Down` and using `countDown.tell(Down.INSTANCE);`]. +In the implementation of the `Behavior` that handle these messages the short names can be used. + +That is a reason for not defining the messages as top level classes in a package. + +An actor typically has a primary `Behavior` or it's only using one `Behavior` and then it's good to define +the messages @scala[in the companion object]@java[as static inner classes] together with that `Behavior`. + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #messages } + +Java +: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #messages } + +Sometimes several actors share the same messages, because they have a tight coupling and using message adapters +would introduce to much boilerplate and duplication. If there is no "natural home" for such messages they can be +be defined in a separate @scala[`object`]@java[`interface`] to give them a naming scope. + +Example of shared message protocol: + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #message-protocol } + +Java +: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #message-protocol } + +## 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`. + +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 +public `Command` @scala[trait]@java[interface]. + +Example of a private visibility for internal message: + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #public-private-messages-1 } + +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 +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: + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #public-private-messages-2 } + +Java +: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #public-private-messages-2 } + +@@@ div {.group-java} + +### Singleton messages + +For messages without parameters the `enum` singleton pattern is recommended: + +Java +: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #message-enum } + +In the `ReceiveBuilder` it can be matched in same way as other messages: + +Java +: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #message-enum-match } + +@@@ + +@@@ div {.group-java} + +## Lamdas versus method references + +It's recommended to keep the message matching with the `ReceiveBuilder` as short and clean as possible +and delegate to methods. This improves readability and ease of method navigation with an IDE. + +The delegation can be with lambdas or [method references](https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html). + +Example of delegation using a lambda: + +Java +: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #on-message-lambda } + +When possible it's preferred to use method references instead of lambdas. The benefit is less verbosity and +in some cases it can actually give better type inference. + +Java +: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #on-message-method-ref } + +`this::onGetValue` is a method reference in above example. It corresponds to `command -> onGetValue(command)`. + +If you are using IntelliJ IDEA it has support for converting lambdas to method references. + +More important than the choice between lambdas or method references is to avoid lambdas with a large block of code. +An anti-pattern would be to inline all message handling inside the lambdas like this: + +Java +: @@snip [StyleGuideDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StyleGuideDocExamples.java) { #on-message-lambda-anti } + +In a real application it would often be more than 3 lines for each message. +It's not only making it more difficult to get an overview of the message matching, but compiler errors related +to lambdas can sometimes be difficult to understand. + +Ideally, lambdas should be written in one line of code. Two lines can be ok, but three is probably too much. +Also, don't use braces and return statements in one-line lambda bodies. + +@@@ + +@@@ div {.group-scala} + +## 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. + +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`. + +The compiler warning if `GetValue` is not handled: + +``` +[warn] ... Counter.scala:45:34: match may not be exhaustive. +[warn] It would fail on the following input: GetValue(_) +[warn] Behaviors.receiveMessage { +[warn] ^ +``` + +Note that a `MatchError` will be thrown at runtime if a message is not handled, so it's important to pay +attention to those. If a `Behavior` should not handle certain messages you can still include them +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 +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 `=>`. + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #pattern-match-without-guard } + +It's recommended to use the `sealed` trait and total functions with exhaustiveness check to detect mistakes +of forgetting to handle some messages. Sometimes that can be inconvenient and then you can use a `PartialFunction` +with `Behaviors.receivePartial` or `Behaviors.receiveMessagePartial` + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #pattern-match-partial } + +@@@ + +@@@ div {.group-scala} + +## ask versus ? + +When using the `AskPattern` it's recommended to use the `ask` method rather than the `?` operator. + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #ask-1 } + +Instead of the `replyTo` you can use `_` for less verbosity. + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #ask-2 } + +When using `?` the following doesn't compile because of type inference problem: + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #ask-3 } + +By adding parentheses it works but is rather ugly, and therefore better to stick with `ask`. + +Scala +: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #ask-4 } + +Note that `AskPattern` is only intended for request-response interaction from outside an actor. If the requester is +inside an actor, prefer `ActorContext.ask` as it provides better thread-safety by not involving +@scala[`Future`]@java[`CompletionStage`] inside the actor. + +@@@ + +## Additional naming conventions + +Some naming conventions have already been mentioned in the context of other recommendations, but here +is a list of additional conventions: + +* `replyTo` is the typical name for the @scala[`ActorRef[Reply]`]@java[`ActorRef`] parameter in + messages to which a reply or acknowledgement should be sent. + +* Incoming messages to an actor are typically called commands, and therefore the super type of all + messages that an actor can handle is typically @scala[`sealed trait Command`]@java[`interface Command {}`]. + +* Use past tense for the events persisted by an `EventSourcedBehavior` since those represent facts that has happened, + e.g. `Incremented`.