Add section on using scala3 union types (#695)

* Add section on using scala3 union types

* Code formatting

* Integrate PR feedback

- Point out that Union types are Scala 3 specific
This commit is contained in:
Eric Loots 2023-10-11 13:19:27 +02:00 committed by GitHub
parent 2ce80b17f7
commit 0a09ccc71e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 59 additions and 7 deletions

View file

@ -138,19 +138,19 @@ class InteractionPatterns3Spec extends ScalaTestWithActorTestKit with AnyWordSpe
sealed trait Command
final case class Translate(site: URI, replyTo: ActorRef[URI]) extends Command
private type CommandAndResponse = Command | Backend.Response
private type CommandAndResponse = Command | Backend.Response // (1)
def apply(backend: ActorRef[Backend.Request]): Behavior[Command] =
def apply(backend: ActorRef[Backend.Request]): Behavior[Command] = // (2)
Behaviors.setup[CommandAndResponse] { context =>
def active(inProgress: Map[Int, ActorRef[URI]], count: Int): Behavior[CommandAndResponse] = {
Behaviors.receiveMessage[CommandAndResponse] {
case Translate(site, replyTo) =>
val taskId = count + 1
backend ! Backend.StartTranslationJob(taskId, site, context.self)
backend ! Backend.StartTranslationJob(taskId, site, context.self) // (3)
active(inProgress.updated(taskId, replyTo), taskId)
case Backend.JobStarted(taskId) =>
case Backend.JobStarted(taskId) => // (4)
context.log.info("Started {}", taskId)
Behaviors.same
case Backend.JobProgress(taskId, progress) =>
@ -164,7 +164,7 @@ class InteractionPatterns3Spec extends ScalaTestWithActorTestKit with AnyWordSpe
}
active(inProgress = Map.empty, count = 0)
}.narrow
}.narrow // (5)
}
// #adapted-response

View file

@ -0,0 +1,44 @@
# Handling responses in Scala 3
Handling responses from other actors in Scala 3 is straightforward and in contrast with
Scala 2, it doesn't require the utilisation of message adapters and response wrappers.
A distinction exists between an actor's public protocol (`Command `) and its internal
protocol (`CommandAndResponse`). The latter is the union of the public protocol and all
the responses the actor should understand. This is union is implemented with Scala 3's
Union types.
**Example:**
![adapted-response.png](./images/adapted-response-scala-3.png)
Scala
: @@snip [InteractionPatternsSpec.scala](/actor-typed-tests/src/test/scala-3/docs/org/apache/pekko/typed/InteractionPatterns3Spec.scala) { #adapted-response }
Let's have a look at the key changes with respect to the Pekko typed implementation in
Scala 2 (see the corresponding numbering in the example code).
* The type `CommandAndResponse` is the union of `Command` and `Backend.Response` (1)
* In the factory method (2) for the `Behavior` of the frontend actor, a
`Behavior[CommandAndResponse]` is narrowed (5) to a `Behavior[Command]`. This works as
the former is able to handle a superset of the messages that can be handled by the latter.
* The sending actor just sends its `self` @apidoc[actor.typed.ActorRef] in the `replyTo`
field of the message (3)
* Responses are handled in a straightforward manner (4)
A more in-depth explanation of the concepts used in applying Scala 3's Union types can
be found in the following blog posts:
* [Using Dotty Union types with Akka Typed](https://blog.lunatech.com/posts/2020-02-12-using-dotty-union-types-with-akka-typed)
* [Using Dotty Union types with Akka Typed - Part II](https://blog.lunatech.com/posts/2020-02-19-using-dotty-union-types-with-akka-typed-part-II)
**Useful when:**
* Subscribing to an actor that will send [many] response messages back
**Problems:**
* It is hard to detect that a message request was not delivered or processed
* Unless the protocol already includes a way to provide context, for example a request id
that is also sent in the response, it is not possible to tie an interaction to some
specific context without introducing a new, separate, actor

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View file

@ -10,6 +10,7 @@ project.description: Using Apache Pekko to build reliable multi-core application
* [actors](actors.md)
* [actor-lifecycle](actor-lifecycle.md)
* [interaction patterns](interaction-patterns.md)
* [handling responses with Scala 3](handling-actor-responses-with-scala3.md)
* [fault-tolerance](fault-tolerance.md)
* [actor-discovery](actor-discovery.md)
* [routers](routers.md)

View file

@ -103,7 +103,8 @@ Java
**Problems:**
* Actors seldom have a response message from another actor as a part of their protocol (see @ref:[adapted response](#adapted-response))
* Actors seldom have a response message from another actor as a part of their protocol as it can be considered
as polluting that protocol with a message from another actor's message (see @ref:[adapted response](#adapted-response))
* It is hard to detect that a message request was not delivered or processed (see @ref:[ask](#request-response-with-ask-between-two-actors))
* Unless the protocol already includes a way to provide context, for example a request id that is also sent in the
response, it is not possible to tie an interaction to some specific context without introducing a new,
@ -112,7 +113,13 @@ Java
## Adapted Response
Most often the sending actor does not, and should not, support receiving the response messages of another actor. In such cases we need to provide an @apidoc[actor.typed.ActorRef] of the right type and adapt the response message to a type that the sending actor can handle.
Most often the sending actor does not, and should not, support receiving the response messages of another actor.
In such cases we need to provide an @apidoc[actor.typed.ActorRef] of the right type and adapt the response message
to a type that the sending actor can handle. In the case of Scala, we need to make a distinction between Scala 2
and Scala 3. In the latter case, we can actually get rid of the need to adapt the response message by leveraging
Scala 3's Union types, which vastly simplifies the handling of responses. The details can be found in the
section @ref:[Handling actor responses in Scala 3](handling-actor-responses-with-scala3.md).
**Example:**