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:
parent
1a7065f09d
commit
2fa39894e6
13 changed files with 665 additions and 100 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
11
akka-docs/src/main/paradox/cluster-sharding-typed.md
Normal file
11
akka-docs/src/main/paradox/cluster-sharding-typed.md
Normal 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$
|
||||
}
|
||||
32
akka-docs/src/main/paradox/cluster-typed.md
Normal file
32
akka-docs/src/main/paradox/cluster-typed.md
Normal 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>
|
||||
```
|
||||
@@@
|
||||
106
akka-docs/src/main/paradox/coexisting.md
Normal file
106
akka-docs/src/main/paradox/coexisting.md
Normal 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.
|
||||
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
@@@
|
||||
|
|
|
|||
14
akka-docs/src/main/paradox/index-typed.md
Normal file
14
akka-docs/src/main/paradox/index-typed.md
Normal 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)
|
||||
|
||||
@@@
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
32
akka-docs/src/main/paradox/persistence-typed.md
Normal file
32
akka-docs/src/main/paradox/persistence-typed.md
Normal 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>
|
||||
```
|
||||
@@@
|
||||
32
akka-docs/src/main/paradox/testing-typed.md
Normal file
32
akka-docs/src/main/paradox/testing-typed.md
Normal 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
|
||||
|
|
@ -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 function’s `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 session’s
|
||||
`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` Actor’s
|
||||
`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.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue