* Add how to compose Partial Behaviors #29832 Refs: #29832 Adds 2 examples on how to compose partial behaviors: - Using `receivePartial` - Using `PartialFunction` Examples are rewritten to follow the main one in the page. Instead of composing through Functions of `Command => Behavior[Command]` I decided to use `Behaviors.receivePartial` as it seems more idiomatic. Adds docs for both examples under the style guide page. * Remove potentially dangerous `receivePartial` composition Example now only showcases `PartialFunction` composition * Remove unused import * Streamline example
This commit is contained in:
parent
2c659a046e
commit
388fb73beb
2 changed files with 78 additions and 0 deletions
|
|
@ -24,6 +24,7 @@ import akka.actor.typed.scaladsl.ActorContext
|
||||||
import akka.actor.typed.scaladsl.Behaviors
|
import akka.actor.typed.scaladsl.Behaviors
|
||||||
//#fun-style
|
//#fun-style
|
||||||
import akka.actor.typed.scaladsl.AbstractBehavior
|
import akka.actor.typed.scaladsl.AbstractBehavior
|
||||||
|
import org.slf4j.Logger
|
||||||
//#oo-style
|
//#oo-style
|
||||||
|
|
||||||
object StyleGuideDocExamples {
|
object StyleGuideDocExamples {
|
||||||
|
|
@ -522,6 +523,54 @@ object StyleGuideDocExamples {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object BehaviorCompositionWithPartialFunction {
|
||||||
|
|
||||||
|
//#messages-sealed-composition
|
||||||
|
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-composition
|
||||||
|
|
||||||
|
//#get-handler-partial
|
||||||
|
def getHandler(value: Int): PartialFunction[Command, Behavior[Command]] = {
|
||||||
|
case GetValue(replyTo) =>
|
||||||
|
replyTo ! Value(value)
|
||||||
|
Behaviors.same
|
||||||
|
}
|
||||||
|
//#get-handler-partial
|
||||||
|
|
||||||
|
//#set-handler-non-zero-partial
|
||||||
|
def setHandlerNotZero(value: Int): PartialFunction[Command, Behavior[Command]] = {
|
||||||
|
case Down =>
|
||||||
|
if (value == 1)
|
||||||
|
zero
|
||||||
|
else
|
||||||
|
nonZero(value - 1)
|
||||||
|
}
|
||||||
|
//#set-handler-non-zero-partial
|
||||||
|
|
||||||
|
//#set-handler-zero-partial
|
||||||
|
def setHandlerZero(log: Logger): PartialFunction[Command, Behavior[Command]] = {
|
||||||
|
case Down =>
|
||||||
|
log.error("Counter is already at zero!")
|
||||||
|
Behaviors.same
|
||||||
|
}
|
||||||
|
//#set-handler-zero-partial
|
||||||
|
|
||||||
|
//#top-level-behaviors-partial
|
||||||
|
val zero: Behavior[Command] = Behaviors.setup { context =>
|
||||||
|
Behaviors.receiveMessagePartial(getHandler(0).orElse(setHandlerZero(context.log)))
|
||||||
|
}
|
||||||
|
|
||||||
|
def nonZero(capacity: Int): Behavior[Command] =
|
||||||
|
Behaviors.receiveMessagePartial(getHandler(capacity).orElse(setHandlerNotZero(capacity)))
|
||||||
|
|
||||||
|
// Default Initial Behavior for this actor
|
||||||
|
def apply(initialCapacity: Int): Behavior[Command] = nonZero(initialCapacity)
|
||||||
|
//#top-level-behaviors-partial
|
||||||
|
}
|
||||||
|
|
||||||
object NestingSample1 {
|
object NestingSample1 {
|
||||||
sealed trait Command
|
sealed trait Command
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -412,6 +412,35 @@ Scala
|
||||||
|
|
||||||
@@@ div {.group-scala}
|
@@@ div {.group-scala}
|
||||||
|
|
||||||
|
## How to compose Partial Functions
|
||||||
|
|
||||||
|
Following up from previous section, there are times when one might want to combine different `PartialFunction`s into one `Behavior`.
|
||||||
|
|
||||||
|
A good use case for composing two or more `PartialFunction`s is when there is a bit of behavior that repeats across different states of the Actor. Below, you can find a simplified example for this use case.
|
||||||
|
|
||||||
|
The Command definition is still highly recommended be kept within a `sealed` Trait:
|
||||||
|
|
||||||
|
Scala
|
||||||
|
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #messages-sealed-composition }
|
||||||
|
|
||||||
|
In this particular case, the Behavior that is repeating over is the one in charge to handle
|
||||||
|
the `GetValue` Command, as it behaves the same regardless of the Actor's internal state.
|
||||||
|
Instead of defining the specific handlers as a `Behavior`, we can define them as a `PartialFunction`:
|
||||||
|
|
||||||
|
Scala
|
||||||
|
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #get-handler-partial #set-handler-non-zero-partial #set-handler-zero-partial }
|
||||||
|
|
||||||
|
Finally, we can go on defining the two different behaviors for this specific actor. For each `Behavior` we would go and concatenate all needed `PartialFunction` instances with `orElse` to finally apply the command to the resulting one:
|
||||||
|
|
||||||
|
Scala
|
||||||
|
: @@snip [StyleGuideDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala) { #top-level-behaviors-partial }
|
||||||
|
|
||||||
|
Even though in this particular example we could use `receiveMessage` as we cover all cases, we use `receiveMessagePartial` instead to cover potential future unhandled message cases.
|
||||||
|
|
||||||
|
@@@
|
||||||
|
|
||||||
|
@@@ div {.group-scala}
|
||||||
|
|
||||||
## ask versus ?
|
## ask versus ?
|
||||||
|
|
||||||
When using the `AskPattern` it's recommended to use the `ask` method rather than the infix `?` operator, like so:
|
When using the `AskPattern` it's recommended to use the `ask` method rather than the infix `?` operator, like so:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue