New docs tree for akka typed (#24182)

* New typed docs tree and coexistence with scala examples
* Coexistence Java examples
* Document how to convert untyped system to typed
This commit is contained in:
Christopher Batey 2017-12-21 10:15:43 +00:00 committed by GitHub
parent 1a7065f09d
commit 2fa39894e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 665 additions and 100 deletions

View file

@ -0,0 +1,87 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package jdocs.akka.typed.coexistence;
import akka.actor.AbstractActor;
import akka.actor.ActorSystem;
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
//#adapter-import
// in Java use the static methods on Adapter to convert from untyped to typed
import akka.actor.typed.javadsl.Adapter;
//#adapter-import
import akka.testkit.javadsl.TestKit;
import akka.testkit.TestProbe;
import org.junit.Test;
import org.scalatest.junit.JUnitSuite;
import scala.concurrent.duration.Duration;
import static akka.actor.typed.javadsl.Actor.same;
import static akka.actor.typed.javadsl.Actor.stopped;
public class TypedWatchingUntypedTest extends JUnitSuite {
//#typed
public static abstract class Typed {
public static class Ping {
public final akka.actor.typed.ActorRef<Pong> replyTo;
public Ping(ActorRef<Pong> replyTo) {
this.replyTo = replyTo;
}
}
interface Command { }
public static class Pong implements Command { }
public static Behavior<Command> behavior() {
return akka.actor.typed.javadsl.Actor.deferred(context -> {
akka.actor.ActorRef second = Adapter.actorOf(context, Untyped.props(), "second");
Adapter.watch(context, second);
second.tell(new Typed.Ping(context.getSelf().narrow()),
Adapter.toUntyped(context.getSelf()));
return akka.actor.typed.javadsl.Actor.immutable(Typed.Command.class)
.onMessage(Typed.Pong.class, (ctx, msg) -> {
Adapter.stop(ctx, second);
return same();
})
.onSignal(akka.actor.typed.Terminated.class, (ctx, sig) -> stopped())
.build();
});
}
}
//#typed
//#untyped
public static class Untyped extends AbstractActor {
public static akka.actor.Props props() {
return akka.actor.Props.create(Untyped.class);
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(Typed.Ping.class, msg -> {
msg.replyTo.tell(new Typed.Pong());
})
.build();
}
}
//#untyped
@Test
public void testItWorks() {
//#create
ActorSystem as = ActorSystem.create();
ActorRef<Typed.Command> typed = Adapter.spawn(as, Typed.behavior(), "Typed");
//#create
TestProbe probe = new TestProbe(as);
probe.watch(Adapter.toUntyped(typed));
probe.expectTerminated(Adapter.toUntyped(typed), Duration.create(1, "second"));
TestKit.shutdownActorSystem(as);
}
}

View file

@ -0,0 +1,99 @@
/**
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
*/
package jdocs.akka.typed.coexistence;
import akka.actor.AbstractActor;
import akka.actor.typed.ActorRef;
import akka.actor.typed.ActorSystem;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.Actor;
//#adapter-import
// In java use the static methods on Adapter to convert from typed to untyped
import akka.actor.typed.javadsl.Adapter;
//#adapter-import
import akka.testkit.TestProbe;
import akka.testkit.javadsl.TestKit;
import org.junit.Test;
import org.scalatest.junit.JUnitSuite;
import scala.concurrent.duration.Duration;
import static akka.actor.typed.javadsl.Actor.same;
public class UntypedWatchingTypedTest extends JUnitSuite {
//#untyped-watch
public static class Untyped extends AbstractActor {
public static akka.actor.Props props() {
return akka.actor.Props.create(Untyped.class);
}
private final akka.actor.typed.ActorRef<Typed.Command> second =
Adapter.spawn(getContext(), Typed.behavior(), "second");
@Override
public void preStart() {
Adapter.watch(getContext(), second);
second.tell(new Typed.Ping(Adapter.toTyped(getSelf())));
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(Typed.Pong.class, msg -> {
Adapter.stop(getContext(), second);
})
.match(akka.actor.Terminated.class, t -> {
getContext().stop(getSelf());
})
.build();
}
}
//#untyped-watch
//#typed
public static abstract class Typed {
interface Command { }
public static class Ping implements Command {
public final akka.actor.typed.ActorRef<Pong> replyTo;
public Ping(ActorRef<Pong> replyTo) {
this.replyTo = replyTo;
}
}
public static class Pong { }
public static Behavior<Command> behavior() {
return Actor.immutable(Typed.Command.class)
.onMessage(Typed.Ping.class, (ctx, msg) -> {
msg.replyTo.tell(new Pong());
return same();
})
.build();
}
}
//#typed
@Test
public void testItWorks() {
//#create-untyped
akka.actor.ActorSystem as = akka.actor.ActorSystem.create();
akka.actor.ActorRef untyped = as.actorOf(Untyped.props());
//#create-untyped
TestProbe probe = new TestProbe(as);
probe.watch(untyped);
probe.expectTerminated(untyped, Duration.create(1, "second"));
TestKit.shutdownActorSystem(as);
}
@Test
public void testConversionFromUnTypedSystemToTyped() {
//#convert-untyped
akka.actor.ActorSystem untypedActorSystem = akka.actor.ActorSystem.create();
ActorSystem<Void> typedActorSystem = Adapter.toTyped(untypedActorSystem);
//#convert-untyped
TestKit.shutdownActorSystem(untypedActorSystem);
}
}

View file

@ -0,0 +1,79 @@
package docs.akka.typed.coexistence
import akka.actor.typed._
import akka.actor.typed.scaladsl.Actor
import akka.testkit.TestKit
//#adapter-import
// adds support for typed actors to an untyped actor system and context
import akka.actor.typed.scaladsl.adapter._
//#adapter-import
import akka.testkit.TestProbe
//#import-alias
import akka.{ actor untyped }
//#import-alias
import org.scalatest.WordSpec
import scala.concurrent.duration._
object TypedWatchingUntypedSpec {
//#typed
object Typed {
final case class Ping(replyTo: akka.actor.typed.ActorRef[Pong.type])
sealed trait Command
case object Pong extends Command
val behavior: Behavior[Command] =
Actor.deferred { context
// context.spawn is an implicit extension method
val untyped = context.actorOf(Untyped.props(), "second")
// context.watch is an implicit extension method
context.watch(untyped)
// illustrating how to pass sender, toUntyped is an implicit extension method
untyped.tell(Typed.Ping(context.self), context.self.toUntyped)
Actor.immutablePartial[Command] {
case (ctx, Pong)
// it's not possible to get the sender, that must be sent in message
// context.stop is an implicit extension method
ctx.stop(untyped)
Actor.same
} onSignal {
case (_, akka.actor.typed.Terminated(_))
Actor.stopped
}
}
}
//#typed
//#untyped
object Untyped {
def props(): untyped.Props = untyped.Props(new Untyped)
}
class Untyped extends untyped.Actor {
override def receive = {
case Typed.Ping(replyTo)
replyTo ! Typed.Pong
}
}
//#untyped
}
class TypedWatchingUntypedSpec extends WordSpec {
import TypedWatchingUntypedSpec._
"Typed -> Untyped" must {
"support creating, watching and messaging" in {
//#create
val system = untyped.ActorSystem("TypedWatchingUntyped")
val typed = system.spawn(Typed.behavior, "Typed")
//#create
val probe = TestProbe()(system)
probe.watch(typed.toUntyped)
probe.expectTerminated(typed.toUntyped, 200.millis)
TestKit.shutdownActorSystem(system)
}
}
}

View file

@ -0,0 +1,93 @@
package docs.akka.typed.coexistence
import akka.actor.typed._
import akka.actor.typed.scaladsl.Actor
import akka.testkit.TestKit
//#adapter-import
// adds support for typed actors to an untyped actor system and context
import akka.actor.typed.scaladsl.adapter._
//#adapter-import
import akka.testkit.TestProbe
//#import-alias
import akka.{ actor untyped }
//#import-alias
import org.scalatest.WordSpec
import scala.concurrent.duration._
object UntypedWatchingTypedSpec {
object Untyped {
def props() = untyped.Props(new Untyped)
}
//#untyped-watch
class Untyped extends untyped.Actor {
// context.spawn is an implicit extension method
val second: ActorRef[Typed.Command] =
context.spawn(Typed.behavior, "second")
// context.watch is an implicit extension method
context.watch(second)
// self can be used as the `replyTo` parameter here because
// there is an implicit conversion from akka.actor.ActorRef to
// akka.actor.typed.ActorRef
second ! Typed.Ping(self)
override def receive = {
case Typed.Pong
println(s"$self got Pong from ${sender()}")
// context.stop is an implicit extension method
context.stop(second)
case untyped.Terminated(ref)
println(s"$self observed termination of $ref")
context.stop(self)
}
}
//#untyped-watch
//#typed
object Typed {
sealed trait Command
final case class Ping(replyTo: ActorRef[Pong.type]) extends Command
case object Pong
val behavior: Behavior[Command] =
Actor.immutable { (ctx, msg)
msg match {
case Ping(replyTo)
println(s"${ctx.self} got Ping from $replyTo")
// replyTo is an untyped actor that has been converted for coexistence
replyTo ! Pong
Actor.same
}
}
}
//#typed
}
class UntypedWatchingTypedSpec extends WordSpec {
import UntypedWatchingTypedSpec._
"Untyped -> Typed" must {
"support creating, watching and messaging" in {
val system = untyped.ActorSystem("Coexistence")
//#create-untyped
val untypedActor = system.actorOf(Untyped.props())
//#create-untyped
val probe = TestProbe()(system)
probe.watch(untypedActor)
probe.expectTerminated(untypedActor, 200.millis)
TestKit.shutdownActorSystem(system)
}
"support converting an untyped actor system to a typed actor system" in {
//#convert-untyped
val system = akka.actor.ActorSystem("UntypedToTypedSystem")
val typedSystem: ActorSystem[Nothing] = system.toTyped
//#convert-untyped
TestKit.shutdownActorSystem(system)
}
}
}

View file

@ -0,0 +1,11 @@
# Sharding
TODO
## Dependency
@@dependency [sbt,Maven,Gradle] {
group=com.typesafe.akka
artifact=akka-cluster-sharding-typed_2.11
version=$version$
}

View file

@ -0,0 +1,32 @@
# Cluster
TODO
## Dependency
sbt
: @@@vars
```
"com.typesafe.akka" %% "akka-cluster-typed" % "$akka.version$"
```
@@@
Gradle
: @@@vars
```
dependencies {
compile group: 'com.typesafe.akka', name: 'akka-cluster-typed_2.11', version: '$akka.version$'
}
```
@@@
Maven
: @@@vars
```
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-cluster-typed_$scala.binary_version$</artifactId>
<version>$akka.version$</version>
</dependency>
```
@@@

View file

@ -0,0 +1,106 @@
# Coexistence
We believe Akka Typed will be adopted in existing systems gradually and therefore it's important to be able to use typed
and untyped actors together, within the same `ActorSystem`. Also, we will not be able to integrate with all existing modules in one big bang release and that is another reason for why these two ways of writing actors must be able to coexist.
There are two different `ActorSystem`s: `akka.actor.ActorSystem` and `akka.actor.typed.ActorSystem`.
The latter should only be used for greenfield projects that are only using typed actors. The `akka.actor.ActorSystem`
can be adapted so that it can create typed actors.
Currently the typed actor system is implemented using an untyped actor system under the hood. This may change in the future.
Typed and untyped can interact the following ways:
* untyped actor systems can create typed actors
* typed actors can send messages to untyped actors, and opposite
* spawn and supervise typed child from untyped parent, and opposite
* watch typed from untyped, and opposite
* untyped actor system can be converted to a typed actor system
@scala[In the examples the `akka.actor` package is aliased to `untyped`.] @java[The examples use fully qualified
class names for the untyped classes to distinguish between typed and untyped classes with the same name.]
Scala
: @@snip [UntypedWatchingTypedSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/UntypedWatchingTypedSpec.scala) { #import-alias }
## Untyped to typed
While coexisting your application will likely still have an untyped ActorSystem. This can be converted to a typed ActorSystem
so that new code and migrated parts don't rely on the untyped system:
Scala
: @@snip [UntypedWatchingTypedSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/UntypedWatchingTypedSpec.scala) { #convert-untyped }
Java
: @@snip [UntypedWatchingTypedTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/coexistence/UntypedWatchingTypedTest.java) { #convert-untyped }
Then for new typed actors here's how you create, watch and send messages to
it from an untyped actor.
Scala
: @@snip [UntypedWatchingTypedSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/UntypedWatchingTypedSpec.scala) { #typed }
Java
: @@snip [UntypedWatchingTypedTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/coexistence/UntypedWatchingTypedTest.java) { #typed }
The top level untyped actor is created in the usual way:
Scala
: @@snip [UntypedWatchingTypedSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/UntypedWatchingTypedSpec.scala) { #create-untyped }
Java
: @@snip [UntypedWatchingTypedTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/coexistence/UntypedWatchingTypedTest.java) { #create-untyped }
Then it can create a typed actor, watch it, and send a message to it:
Scala
: @@snip [UntypedWatchingTypedSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/UntypedWatchingTypedSpec.scala) { #untyped-watch }
Java
: @@snip [UntypedWatchingTypedTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/coexistence/UntypedWatchingTypedTest.java) { #untyped-watch }
@scala[There is one `import` that is needed to make that work.] @java[We import the Adapter class and
call static methods for conversion.]
Scala
: @@snip [UntypedWatchingTypedSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/UntypedWatchingTypedSpec.scala) { #adapter-import }
Java
: @@snip [UntypedWatchingTypedTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/coexistence/UntypedWatchingTypedTest.java) { #adapter-import }
@scala[That adds some implicit extension methods that are added to untyped and typed `ActorSystem` and `ActorContext` in both directions.]
@java[To convert between typed and untyped there are adapter methods in `akka.typed.javadsl.Adapter`.] Note the inline comments in the example above.
## Typed to untyped
Let's turn the example upside down and first start the typed actor and then the untyped as a child.
The following will show how to create, watch and send messages back and forth from a typed actor to this
untyped actor:
Scala
: @@snip [TypedWatchingUntypedSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/TypedWatchingUntypedSpec.scala) { #untyped }
Java
: @@snip [TypedWatchingUntypedTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/coexistence/TypedWatchingUntypedTest.java) { #untyped }
Creating the actor system and the typed actor:
Scala
: @@snip [TypedWatchingUntypedSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/TypedWatchingUntypedSpec.scala) { #create }
Java
: @@snip [TypedWatchingUntypedTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/coexistence/TypedWatchingUntypedTest.java) { #create }
Then the typed actor creates the untyped actor, watches it and sends and receives a response:
Scala
: @@snip [TypedWatchingUntypedSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/TypedWatchingUntypedSpec.scala) { #typed }
Java
: @@snip [TypedWatchingUntypedTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/coexistence/TypedWatchingUntypedTest.java) { #typed }
There is one caveat regarding supervision of untyped child from typed parent. If the child throws an exception we would expect it to be restarted, but supervision in Akka Typed defaults to stopping the child in case it fails. The restarting facilities in Akka Typed will not work with untyped children. However, the workaround is simply to add another untyped actor that takes care of the supervision, i.e. restarts in case of failure if that is the desired behavior.

View file

@ -5,7 +5,6 @@
@@@ index
* [actors](actors.md)
* [typed](typed.md)
* [fault-tolerance](fault-tolerance.md)
* [dispatchers](dispatchers.md)
* [mailboxes](mailboxes.md)
@ -16,6 +15,5 @@
* [persistence-query](persistence-query.md)
* [persistence-query-leveldb](persistence-query-leveldb.md)
* [testing](testing.md)
* [typed-actors](typed-actors.md)
@@@

View file

@ -0,0 +1,14 @@
# Akka Typed
@@toc { depth=2 }
@@@ index
* [actors](typed.md)
* [coexisting](coexisting.md)
* [cluster](cluster-typed.md)
* [cluster-sharding](cluster-sharding-typed.md)
* [persistence](persistence-typed.md)
* [testing](testing-typed.md)
@@@

View file

@ -8,6 +8,7 @@
* [guide/index](guide/index.md)
* [general/index](general/index.md)
* [index-actors](index-actors.md)
* [index-typed](index-typed.md)
* [index-cluster](index-cluster.md)
* [stream/index](stream/index.md)
* [index-network](index-network.md)

View file

@ -0,0 +1,32 @@
# Persistence
TODO
## Dependency
sbt
: @@@vars
```
"com.typesafe.akka" %% "akka-persistence-typed" % "$akka.version$"
```
@@@
Gradle
: @@@vars
```
dependencies {
compile group: 'com.typesafe.akka', name: 'akka-persistence-typed_2.11', version: '$akka.version$'
}
```
@@@
Maven
: @@@vars
```
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-persistence-typed_$scala.binary_version$</artifactId>
<version>$akka.version$</version>
</dependency>
```
@@@

View file

@ -0,0 +1,32 @@
# Testing
## Dependency
sbt
: @@@vars
```
"com.typesafe.akka" %% "akka-testkit-typed" % "$akka.version$"
```
@@@
Gradle
: @@@vars
```
dependencies {
compile group: 'com.typesafe.akka', name: 'akka-testkit-typed_2.11', version: '$akka.version$'
}
```
@@@
Maven
: @@@vars
```
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-testkit-typed_$scala.binary_version$</artifactId>
<version>$akka.version$</version>
</dependency>
```
@@@
TODO

View file

@ -1,4 +1,4 @@
# Akka Typed
# Actors
@@@ warning
@ -11,41 +11,21 @@ This module is currently marked as @ref:[may change](common/may-change.md) in th
## Dependency
Akka Typed APIs for each akka module are in a `akka-$module-typed` e.g. `akka-actor-typed` `akka-persistence-typed`
For the following examples make sure that you have the following dependency in your project:
To use typed actors add the following dependency:
sbt
: @@@vars
```
"com.typesafe.akka" %% "akka-actor-typed" % "$akka.version$"
```
@@@
Gradle
: @@@vars
```
dependencies {
compile group: 'com.typesafe.akka', name: 'akka-actor-typed_2.11', version: '$akka.version$'
}
```
@@@
Maven
: @@@vars
```
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor-typed_$scala.binary_version$</artifactId>
<version>$akka.version$</version>
</dependency>
```
@@@
@@dependency [sbt,Maven,Gradle] {
group=com.typesafe.akka
artifact=akka-actor-typed_2.11
version=$version$
}
## Introduction
As discussed in @ref:[Actor Systems](general/actor-systems.md) (and following chapters) Actors are about
As discussed in @ref:[Actor Systems](general/actor-systems.md) Actors are about
sending messages between independent units of computation, but how does that
look like? In all of the following these imports are assumed:
look like?
In all of the following these imports are assumed:
Scala
: @@snip [IntroSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #imports }
@ -78,7 +58,7 @@ behavior that holds the new immutable state. In this case we don't need to
update any state, so we return `Same`.
The type of the messages handled by this behavior is declared to be of class
`Greet`, which implies that the supplied functions `msg` argument is
`Greet`, meaning that `msg` argument is
also typed as such. This is why we can access the `whom` and `replyTo`
members without needing to use a pattern match.
@ -138,8 +118,6 @@ the reply message to that `ActorRef` the message that fulfills the
We use this here to send the `Greet` command to the Actor and when the
reply comes back we will print it out and tell the actor system to shut down.
Once that is done as well we print the `"system terminated"` messages and the
program ends.
@@@ div {.group-scala}
@ -151,68 +129,17 @@ a timeout then no `greeting` would be extracted and nothing would happen.
@@@
This shows that there are aspects of Actor messaging that can be type-checked
by the compiler, but this ability is not unlimited, there are bounds to what we
can statically express. Before we go on with a more complex (and realistic)
example we make a small detour to highlight some of the theory behind this.
## A Little Bit of Theory
The [Actor Model](http://en.wikipedia.org/wiki/Actor_model) as defined by
Hewitt, Bishop and Steiger in 1973 is a computational model that expresses
exactly what it means for computation to be distributed. The processing
units—Actors—can only communicate by exchanging messages and upon reception of a
message an Actor can do the following three fundamental actions:
1. send a finite number of messages to Actors it knows
2. create a finite number of new Actors
3. designate the behavior to be applied to the next message
The Akka Typed project expresses these actions using behaviors and addresses.
Messages can be sent to an address and behind this façade there is a behavior
that receives the message and acts upon it. The binding between address and
behavior can change over time as per the third point above, but that is not
visible on the outside.
With this preamble we can get to the unique property of this project, namely
that it introduces static type checking to Actor interactions: addresses are
parameterized and only messages that are of the specified type can be sent to
them. The association between an address and its type parameter must be made
when the address (and its Actor) is created. For this purpose each behavior is
also parameterized with the type of messages it is able to process. Since the
behavior can change behind the address façade, designating the next behavior is
a constrained operation: the successor must handle the same type of messages as
its predecessor. This is necessary in order to not invalidate the addresses
that refer to this Actor.
What this enables is that whenever a message is sent to an Actor we can
statically ensure that the type of the message is one that the Actor declares
to handle—we can avoid the mistake of sending completely pointless messages.
What we cannot statically ensure, though, is that the behavior behind the
address will be in a given state when our message is received. The fundamental
reason is that the association between address and behavior is a dynamic
runtime property, the compiler cannot know it while it translates the source
code.
This is the same as for normal Java objects with internal variables: when
compiling the program we cannot know what their value will be, and if the
result of a method call depends on those variables then the outcome is
uncertain to a degree—we can only be certain that the returned value is of a
given type.
We have seen above that the return type of an Actor command is described by the
type of reply-to address that is contained within the message. This allows a
conversation to be described in terms of its types: the reply will be of type
A, but it might also contain an address of type B, which then allows the other
Actor to continue the conversation by sending a message of type B to this new
address. While we cannot statically express the “current” state of an Actor, we
can express the current state of a protocol between two Actors, since that is
just given by the last message type that was received or sent.
In the next section we demonstrate this on a more realistic example.
## A More Complex Example
The next example demonstrates some important patterns:
* Using a sealed trait and case class/objects to represent multiple messages an actor can receive
* Handle incoming messages of different types by using `adapter`s
* Handling state by changing behavior
* Using multiple typed actors to represent different parts of a protocol in a type safe way
Consider an Actor that runs a chat room: client Actors may connect by sending
a message that contains their screen name and then they can post messages. The
chat room Actor will disseminate all posted messages to all currently connected
@ -236,8 +163,7 @@ that the client has revealed its own address, via the `replyTo` argument, so tha
This illustrates how Actors can express more than just the equivalent of method
calls on Java objects. The declared message types and their contents describe a
full protocol that can involve multiple Actors and that can evolve over
multiple steps. The implementation of the chat room protocol would be as simple
as the following:
multiple steps. Here's the implementation of the chat room protocol:
Scala
: @@snip [IntroSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala) { #chatroom-behavior }
@ -245,10 +171,10 @@ Scala
Java
: @@snip [IntroSpec.scala]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/IntroTest.java) { #chatroom-behavior }
The core of this behavior is stateful, the chat room itself does not change
into something else when sessions are established, but we introduce a variable
that tracks the opened sessions. Note that by using a method parameter a `var`
is not needed. When a new `GetSession` command comes in we add that client to the
The state is managed by changing behavior rather than using any variables.
When a new `GetSession` command comes in we add that client to the
list that is in the returned behavior. Then we also need to create the sessions
`ActorRef` that will be used to post messages. In this case we want to
create a very simple Actor that just repackages the `PostMessage`
@ -340,6 +266,7 @@ the main Actor terminates there is nothing more to do.
Therefore after creating the Actor system with the `main` Actors
`Behavior` we just await its termination.
## Status of this Project and Relation to Akka Actors
Akka Typed is the result of many years of research and previous attempts
@ -375,3 +302,57 @@ having to worry about timeouts and spurious failures. Another side-effect is
that behaviors can nicely be composed and decorated, see `tap`, or
@scala[`widen`]@java[`widened`] combinators; nothing about these is special or internal, new
combinators can be written as external libraries or tailor-made for each project.
## A Little Bit of Theory
The [Actor Model](http://en.wikipedia.org/wiki/Actor_model) as defined by
Hewitt, Bishop and Steiger in 1973 is a computational model that expresses
exactly what it means for computation to be distributed. The processing
units—Actors—can only communicate by exchanging messages and upon reception of a
message an Actor can do the following three fundamental actions:
1. send a finite number of messages to Actors it knows
2. create a finite number of new Actors
3. designate the behavior to be applied to the next message
The Akka Typed project expresses these actions using behaviors and addresses.
Messages can be sent to an address and behind this façade there is a behavior
that receives the message and acts upon it. The binding between address and
behavior can change over time as per the third point above, but that is not
visible on the outside.
With this preamble we can get to the unique property of this project, namely
that it introduces static type checking to Actor interactions: addresses are
parameterized and only messages that are of the specified type can be sent to
them. The association between an address and its type parameter must be made
when the address (and its Actor) is created. For this purpose each behavior is
also parameterized with the type of messages it is able to process. Since the
behavior can change behind the address façade, designating the next behavior is
a constrained operation: the successor must handle the same type of messages as
its predecessor. This is necessary in order to not invalidate the addresses
that refer to this Actor.
What this enables is that whenever a message is sent to an Actor we can
statically ensure that the type of the message is one that the Actor declares
to handle—we can avoid the mistake of sending completely pointless messages.
What we cannot statically ensure, though, is that the behavior behind the
address will be in a given state when our message is received. The fundamental
reason is that the association between address and behavior is a dynamic
runtime property, the compiler cannot know it while it translates the source
code.
This is the same as for normal Java objects with internal variables: when
compiling the program we cannot know what their value will be, and if the
result of a method call depends on those variables then the outcome is
uncertain to a degree—we can only be certain that the returned value is of a
given type.
We have seen above that the return type of an Actor command is described by the
type of reply-to address that is contained within the message. This allows a
conversation to be described in terms of its types: the reply will be of type
A, but it might also contain an address of type B, which then allows the other
Actor to continue the conversation by sending a message of type B to this new
address. While we cannot statically express the “current” state of an Actor, we
can express the current state of a protocol between two Actors, since that is
just given by the last message type that was received or sent.