Merge pull request #27337 from akka/wip-22805-style4-patriknw

Style Guide: enum msg, ask, tot fun, naming, meth ref, intro, #22805
This commit is contained in:
Patrik Nordwall 2019-07-12 19:21:02 +02:00 committed by GitHub
commit d27be3fa48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 857 additions and 13 deletions

View file

@ -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<Counter.Command> {
public interface Command {}
// #message-enum
public enum Increment implements Command {
INSTANCE
}
// #message-enum
public static class GetValue implements Command {
public final ActorRef<Value> replyTo;
@ -99,6 +104,7 @@ interface StyleGuideDocExamples {
this.value = value;
}
}
// #messages
public static Behavior<Command> create() {
return Behaviors.setup(Counter::new);
@ -115,7 +121,9 @@ interface StyleGuideDocExamples {
@Override
public Receive<Command> 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<CountDown.Command> {
public interface Command {}
public enum Down implements Command {
INSTANCE
}
// factory for the initial `Behavior`
public static Behavior<Command> create(int countDownFrom, ActorRef<Done> notifyWhenZero) {
return Behaviors.setup(context -> new CountDown(countDownFrom, notifyWhenZero));
}
private final ActorRef<Done> notifyWhenZero;
private int remaining;
private CountDown(int countDownFrom, ActorRef<Done> notifyWhenZero) {
this.remaining = countDownFrom;
this.notifyWhenZero = notifyWhenZero;
}
@Override
public Receive<Command> createReceive() {
return newReceiveBuilder().onMessage(Down.class, notUsed -> onDown()).build();
}
private Behavior<Command> 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<Done> doneRef = null;
{
// #behavior-factory-method-spawn
ActorRef<CountDown.Command> 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<OperationResult> replyTo;
public Increment(int delta, ActorRef<OperationResult> replyTo) {
this.delta = delta;
this.replyTo = replyTo;
}
}
public static class Decrement implements Command {
public final int delta;
private final ActorRef<OperationResult> replyTo;
public Decrement(int delta, ActorRef<OperationResult> 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<Counter.Command> {
public interface Command {}
public enum Increment implements Command {
INSTANCE
}
public static class GetValue implements Command {
public final ActorRef<Value> replyTo;
public GetValue(ActorRef<Value> 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<Command> 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<Command> context;
private int count;
private Counter(String name, ActorContext<Command> context) {
this.name = name;
this.context = context;
}
// #on-message-lambda
// #on-message-method-ref
@Override
// #on-message-lambda-anti
public Receive<Command> 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<Command> onIncrement() {
count++;
context.getLog().debug("[{}] Incremented counter to [{}]", name, count);
return this;
}
// #on-message-lambda
private Behavior<Command> onTick() {
count++;
context.getLog().debug("[{}] Incremented counter by background tick to [{}]", name, count);
return this;
}
// #on-message-method-ref
private Behavior<Command> 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<Command> 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<Counter.PrivateCommand> {
public interface PrivateCommand {}
public interface Command extends PrivateCommand {}
public enum Increment implements Command {
INSTANCE
}
public static class GetValue implements Command {
public final ActorRef<Value> replyTo;
public GetValue(ActorRef<Value> 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<Command>
enum Tick implements PrivateCommand {
INSTANCE
}
public static Behavior<Command> create(String name, Duration tickInterval) {
return Behaviors.setup(
(ActorContext<PrivateCommand> 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<PrivateCommand> context;
private int count;
private Counter(String name, ActorContext<PrivateCommand> context) {
this.name = name;
this.context = context;
}
@Override
public Receive<PrivateCommand> createReceive() {
return newReceiveBuilder()
.onMessage(Increment.class, notUsed -> onIncrement())
.onMessage(Tick.class, notUsed -> onTick())
.onMessage(GetValue.class, this::onGetValue)
.build();
}
private Behavior<PrivateCommand> onIncrement() {
count++;
context.getLog().debug("[{}] Incremented counter to [{}]", name, count);
return this;
}
private Behavior<PrivateCommand> onTick() {
count++;
context.getLog().debug("[{}] Incremented counter by background tick to [{}]", name, count);
return this;
}
private Behavior<PrivateCommand> onGetValue(GetValue command) {
command.replyTo.tell(new Value(count));
return this;
}
}
// #public-private-messages-2
}
}

View file

@ -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
}
}
}
}

View file

@ -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]]
*/

View file

@ -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]]
*/

View file

@ -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<Reply>`] 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`.