Cluster typed examples in scala and java
This commit is contained in:
parent
f17dc5c7f7
commit
45c7303d3f
10 changed files with 144 additions and 36 deletions
|
|
@ -7,10 +7,12 @@ import scala.concurrent.{ Await, Future }
|
|||
import scala.concurrent.duration._
|
||||
import akka.testkit.TimingTest
|
||||
|
||||
import scala.concurrent.duration.Duration.DurationIsOrdered
|
||||
|
||||
class ConstantRateEntityRecoveryStrategySpec extends AkkaSpec {
|
||||
|
||||
import system.dispatcher
|
||||
|
||||
/*
|
||||
val strategy = EntityRecoveryStrategy.constantStrategy(system, 1.second, 2)
|
||||
"ConstantRateEntityRecoveryStrategy" must {
|
||||
"recover entities" taggedAs TimingTest in {
|
||||
|
|
@ -42,4 +44,5 @@ class ConstantRateEntityRecoveryStrategySpec extends AkkaSpec {
|
|||
result.size should ===(0)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,14 @@ final case class Subscribe[A <: ClusterDomainEvent](
|
|||
subscriber: ActorRef[A],
|
||||
eventClass: Class[A]) extends ClusterStateSubscription
|
||||
|
||||
object Subscribe {
|
||||
/**
|
||||
* Java API
|
||||
*/
|
||||
def create[A <: ClusterDomainEvent](subscriber: ActorRef[A], eventClass: Class[A]): Subscribe[A] =
|
||||
Subscribe(subscriber, eventClass)
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to this node being up, after sending this event the subscription is automatically
|
||||
* cancelled. If the node is already up the event is also sent to the subscriber. If the node was up
|
||||
|
|
@ -73,6 +81,13 @@ sealed trait ClusterCommand
|
|||
*/
|
||||
final case class Join(address: Address) extends ClusterCommand
|
||||
|
||||
object Join {
|
||||
/**
|
||||
* Java API
|
||||
*/
|
||||
def create(address: Address): Join = Join(address)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scala API: Join the specified seed nodes without defining them in config.
|
||||
* Especially useful from tests when Addresses are unknown before startup time.
|
||||
|
|
@ -111,6 +126,13 @@ final case class JoinSeedNodes(seedNodes: immutable.Seq[Address]) extends Cluste
|
|||
*/
|
||||
final case class Leave(address: Address) extends ClusterCommand
|
||||
|
||||
object Leave {
|
||||
/**
|
||||
* Java API
|
||||
*/
|
||||
def create(address: Address): Leave = Leave(address)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send command to DOWN the node specified by 'address'.
|
||||
*
|
||||
|
|
@ -128,6 +150,9 @@ object Cluster extends ExtensionId[Cluster] {
|
|||
|
||||
def createExtension(system: ActorSystem[_]): Cluster = new AdapterClusterImpl(system)
|
||||
|
||||
/**
|
||||
* Java API
|
||||
*/
|
||||
def get(system: ActorSystem[_]): Cluster = apply(system)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
package jdocs.akka.cluster.typed;
|
||||
|
||||
//#cluster-imports
|
||||
|
||||
import akka.actor.typed.*;
|
||||
import akka.actor.typed.javadsl.*;
|
||||
import akka.cluster.ClusterEvent;
|
||||
import akka.cluster.typed.*;
|
||||
//#cluster-imports
|
||||
import akka.testkit.typed.javadsl.TestProbe;
|
||||
import docs.akka.cluster.typed.BasicClusterManualSpec;
|
||||
|
||||
//FIXME make these tests
|
||||
public class BasicClusterExampleTest {
|
||||
public void clusterApiExample() {
|
||||
ActorSystem<Object> system = ActorSystem.create(Actor.empty(), "ClusterSystem", BasicClusterManualSpec.clusterConfig());
|
||||
ActorSystem<Object> system2 = ActorSystem.create(Actor.empty(), "ClusterSystem", BasicClusterManualSpec.clusterConfig());
|
||||
|
||||
try {
|
||||
//#cluster-create
|
||||
Cluster cluster = Cluster.get(system);
|
||||
//#cluster-create
|
||||
Cluster cluster2 = Cluster.get(system2);
|
||||
|
||||
//#cluster-join
|
||||
cluster.manager().tell(Join.create(cluster.selfMember().address()));
|
||||
//#cluster-join
|
||||
|
||||
//#cluster-leave
|
||||
cluster2.manager().tell(Leave.create(cluster2.selfMember().address()));
|
||||
//#cluster-leave
|
||||
} finally {
|
||||
system.terminate();
|
||||
system2.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
public void clusterLeave() throws Exception {
|
||||
ActorSystem<Object> system = ActorSystem.create(Actor.empty(), "ClusterSystem", BasicClusterManualSpec.clusterConfig());
|
||||
ActorSystem<Object> system2 = ActorSystem.create(Actor.empty(), "ClusterSystem", BasicClusterManualSpec.clusterConfig());
|
||||
|
||||
try {
|
||||
Cluster cluster = Cluster.get(system);
|
||||
Cluster cluster2 = Cluster.get(system2);
|
||||
|
||||
//#cluster-subscribe
|
||||
TestProbe<ClusterEvent.MemberEvent> testProbe = new TestProbe<>(system);
|
||||
cluster.subscriptions().tell(Subscribe.create(testProbe.ref(), ClusterEvent.MemberEvent.class));
|
||||
//#cluster-subscribe
|
||||
|
||||
//#cluster-leave-example
|
||||
cluster.manager().tell(Leave.create(cluster2.selfMember().address()));
|
||||
testProbe.expectMsgType(ClusterEvent.MemberLeft.class);
|
||||
testProbe.expectMsgType(ClusterEvent.MemberExited.class);
|
||||
testProbe.expectMsgType(ClusterEvent.MemberRemoved.class);
|
||||
//#cluster-leave-example
|
||||
|
||||
} finally {
|
||||
system.terminate();
|
||||
system2.terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import akka.actor.typed.ActorSystem;
|
|||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.Actor;
|
||||
import akka.actor.typed.receptionist.Receptionist;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.duration.Duration;
|
||||
|
||||
|
|
@ -68,7 +67,6 @@ public class ReceptionistExampleTest {
|
|||
//#pinger-guardian
|
||||
}
|
||||
|
||||
@Test
|
||||
public void workPlease() throws Exception {
|
||||
ActorSystem<Receptionist.Listing<PingPongExample.Ping>> system =
|
||||
ActorSystem.create(PingPongExample.guardian(), "ReceptionistExample");
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ package akka.cluster.typed.internal.receptionist
|
|||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import akka.actor.ExtendedActorSystem
|
||||
import akka.actor.typed.{ActorRef, ActorRefResolver, TypedAkkaSpecWithShutdown}
|
||||
import akka.actor.typed.{ ActorRef, ActorRefResolver, TypedAkkaSpecWithShutdown }
|
||||
import akka.actor.typed.internal.adapter.ActorSystemAdapter
|
||||
import akka.actor.typed.receptionist.Receptionist
|
||||
import akka.actor.typed.scaladsl.Actor
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import akka.cluster.Cluster
|
||||
import akka.serialization.SerializerWithStringManifest
|
||||
import akka.testkit.typed.{TestKit, TestKitSettings}
|
||||
import akka.testkit.typed.{ TestKit, TestKitSettings }
|
||||
import akka.testkit.typed.scaladsl.TestProbe
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
|
|
@ -65,14 +65,14 @@ object ClusterReceptionistSpec {
|
|||
def identifier: Int = 47
|
||||
def manifest(o: AnyRef): String = o match {
|
||||
case _: Ping ⇒ "a"
|
||||
case Pong ⇒ "b"
|
||||
case Perish ⇒ "c"
|
||||
case Pong ⇒ "b"
|
||||
case Perish ⇒ "c"
|
||||
}
|
||||
|
||||
def toBinary(o: AnyRef): Array[Byte] = o match {
|
||||
case p: Ping ⇒ ActorRefResolver(system.toTyped).toSerializationFormat(p.respondTo).getBytes(StandardCharsets.UTF_8)
|
||||
case Pong ⇒ Array.emptyByteArray
|
||||
case Perish ⇒ Array.emptyByteArray
|
||||
case Pong ⇒ Array.emptyByteArray
|
||||
case Perish ⇒ Array.emptyByteArray
|
||||
}
|
||||
|
||||
def fromBinary(bytes: Array[Byte], manifest: String): AnyRef = manifest match {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package docs.akka.cluster.typed
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
import org.scalatest.{ Matchers, WordSpec }
|
||||
//#cluster-imports
|
||||
import akka.actor.typed._
|
||||
import akka.actor.typed.scaladsl._
|
||||
|
|
@ -10,8 +10,8 @@ import akka.cluster.MemberStatus
|
|||
import akka.cluster.typed._
|
||||
//#cluster-imports
|
||||
import akka.testkit.typed.scaladsl.TestProbe
|
||||
import org.scalatest.concurrent.{Eventually, ScalaFutures}
|
||||
import org.scalatest.time.{Millis, Seconds, Span}
|
||||
import org.scalatest.concurrent.{ Eventually, ScalaFutures }
|
||||
import org.scalatest.time.{ Millis, Seconds, Span }
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import akka.actor.typed.scaladsl.adapter._
|
|||
import akka.serialization.SerializerWithStringManifest
|
||||
import docs.akka.cluster.typed.PingPongExample._
|
||||
|
||||
|
||||
//#serializer
|
||||
class PingSerializer(system: ExtendedActorSystem) extends SerializerWithStringManifest {
|
||||
private val actorRefResolver = ActorRefResolver(system.toTyped)
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import java.util.concurrent.ThreadLocalRandom
|
|||
import akka.actor.Address
|
||||
import akka.actor.typed._
|
||||
import akka.actor.typed.receptionist.Receptionist
|
||||
import akka.actor.typed.receptionist.Receptionist.{Find, Listing, ServiceKey}
|
||||
import akka.actor.typed.receptionist.Receptionist.{ Find, Listing, ServiceKey }
|
||||
import akka.actor.typed.scaladsl._
|
||||
import akka.cluster.ClusterEvent._
|
||||
import akka.cluster.typed.{Cluster, Join, Subscribe}
|
||||
import akka.cluster.typed.{ Cluster, Join, Subscribe }
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.concurrent.ScalaFutures
|
||||
|
|
@ -26,7 +26,7 @@ object RandomRouter {
|
|||
msg match {
|
||||
case Listing(_, services: Set[ActorRef[T]]) ⇒
|
||||
routingBehavior(services.toVector)
|
||||
case other: T@unchecked ⇒
|
||||
case other: T @unchecked ⇒
|
||||
if (routees.isEmpty)
|
||||
Actor.unhandled
|
||||
else {
|
||||
|
|
@ -59,20 +59,20 @@ object RandomRouter {
|
|||
msg match {
|
||||
case Listing(_, services: Set[ActorRef[T]]) ⇒
|
||||
routingBehavior(services.toVector, unreachable)
|
||||
case WrappedReachabilityEvent(event) => event match {
|
||||
case UnreachableMember(m) =>
|
||||
case WrappedReachabilityEvent(event) ⇒ event match {
|
||||
case UnreachableMember(m) ⇒
|
||||
routingBehavior(routees, unreachable + m.address)
|
||||
case ReachableMember(m) =>
|
||||
case ReachableMember(m) ⇒
|
||||
routingBehavior(routees, unreachable - m.address)
|
||||
}
|
||||
|
||||
case other: T@unchecked ⇒
|
||||
case other: T @unchecked ⇒
|
||||
if (routees.isEmpty)
|
||||
Actor.unhandled
|
||||
else {
|
||||
val reachableRoutes =
|
||||
if (unreachable.isEmpty) routees
|
||||
else routees.filterNot { r => unreachable(r.path.address) }
|
||||
else routees.filterNot { r ⇒ unreachable(r.path.address) }
|
||||
|
||||
val i = ThreadLocalRandom.current.nextInt(reachableRoutes.size)
|
||||
reachableRoutes(i) ! other
|
||||
|
|
@ -106,9 +106,9 @@ object PingPongExample {
|
|||
//#ping-service
|
||||
|
||||
//#pinger
|
||||
def pinger(pingService: ActorRef[Ping]) = Actor.deferred[Pong.type] { ctx =>
|
||||
def pinger(pingService: ActorRef[Ping]) = Actor.deferred[Pong.type] { ctx ⇒
|
||||
pingService ! Ping(ctx.self)
|
||||
Actor.immutable { (_, msg) =>
|
||||
Actor.immutable { (_, msg) ⇒
|
||||
println("I was ponged!!" + msg)
|
||||
Actor.same
|
||||
}
|
||||
|
|
@ -116,16 +116,16 @@ object PingPongExample {
|
|||
//#pinger
|
||||
|
||||
//#pinger-guardian
|
||||
val guardian: Behavior[Listing[Ping]] = Actor.deferred { ctx =>
|
||||
val guardian: Behavior[Listing[Ping]] = Actor.deferred { ctx ⇒
|
||||
ctx.system.receptionist ! Receptionist.Subscribe(PingServiceKey, ctx.self)
|
||||
val ps = ctx.spawnAnonymous(pingService)
|
||||
ctx.watch(ps)
|
||||
Actor.immutablePartial[Listing[Ping]] {
|
||||
case (c, Listing(PingServiceKey, listings)) if listings.nonEmpty =>
|
||||
listings.foreach(ps => ctx.spawnAnonymous(pinger(ps)))
|
||||
case (c, Listing(PingServiceKey, listings)) if listings.nonEmpty ⇒
|
||||
listings.foreach(ps ⇒ ctx.spawnAnonymous(pinger(ps)))
|
||||
Actor.same
|
||||
} onSignal {
|
||||
case (_, Terminated(`ps`)) =>
|
||||
case (_, Terminated(`ps`)) ⇒
|
||||
println("Ping service has shut down")
|
||||
Actor.stopped
|
||||
}
|
||||
|
|
@ -133,15 +133,15 @@ object PingPongExample {
|
|||
//#pinger-guardian
|
||||
|
||||
//#pinger-guardian-pinger-service
|
||||
val guardianJustPingService: Behavior[Listing[Ping]] = Actor.deferred { ctx =>
|
||||
val guardianJustPingService: Behavior[Listing[Ping]] = Actor.deferred { ctx ⇒
|
||||
val ps = ctx.spawnAnonymous(pingService)
|
||||
ctx.watch(ps)
|
||||
Actor.immutablePartial[Listing[Ping]] {
|
||||
case (c, Listing(PingServiceKey, listings)) if listings.nonEmpty =>
|
||||
listings.foreach(ps => ctx.spawnAnonymous(pinger(ps)))
|
||||
case (c, Listing(PingServiceKey, listings)) if listings.nonEmpty ⇒
|
||||
listings.foreach(ps ⇒ ctx.spawnAnonymous(pinger(ps)))
|
||||
Actor.same
|
||||
} onSignal {
|
||||
case (_, Terminated(`ps`)) =>
|
||||
case (_, Terminated(`ps`)) ⇒
|
||||
println("Ping service has shut down")
|
||||
Actor.stopped
|
||||
}
|
||||
|
|
@ -149,11 +149,11 @@ object PingPongExample {
|
|||
//#pinger-guardian-pinger-service
|
||||
|
||||
//#pinger-guardian-just-pinger
|
||||
val guardianJustPinger: Behavior[Listing[Ping]] = Actor.deferred { ctx =>
|
||||
val guardianJustPinger: Behavior[Listing[Ping]] = Actor.deferred { ctx ⇒
|
||||
ctx.system.receptionist ! Receptionist.Subscribe(PingServiceKey, ctx.self)
|
||||
Actor.immutablePartial[Listing[Ping]] {
|
||||
case (c, Listing(PingServiceKey, listings)) if listings.nonEmpty =>
|
||||
listings.foreach(ps => ctx.spawnAnonymous(pinger(ps)))
|
||||
case (c, Listing(PingServiceKey, listings)) if listings.nonEmpty ⇒
|
||||
listings.foreach(ps ⇒ ctx.spawnAnonymous(pinger(ps)))
|
||||
Actor.same
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,4 +53,4 @@ guardian actor spawns a pinger to ping it.
|
|||
## Cluster Receptionist
|
||||
|
||||
The `Receptionist` also works in a cluster, the state for the receptionist is propagated via @ref:[distributed data](distributed-data.md).
|
||||
The only difference is the serialisation concerns, see @ref:[clustering](cluster-typed.md).
|
||||
The only difference is the serialisation concerns, see @ref:[clustering](cluster-typed.md#serialization).
|
||||
|
|
|
|||
|
|
@ -23,10 +23,16 @@ Cluster API. All of the examples below assume the following imports:
|
|||
Scala
|
||||
: @@snip [BasicClusterExampleSpec.scala]($akka$/akka-cluster-typed/src/test/scala/docs/akka/cluster/typed/BasicClusterExampleSpec.scala) { #cluster-imports }
|
||||
|
||||
Java
|
||||
: @@snip [BasicClusterExampleTest.java]($akka$/akka-cluster-typed/src/test/java/jdocs/akka/cluster/typed/BasicClusterExampleTest.java) { #cluster-imports }
|
||||
|
||||
And the minimum configuration required is to set a host/port for remoting and the `cluster`
|
||||
|
||||
Scala
|
||||
: @@snip [BasicClusterExampleSpec.scala]($akka$/akka-cluster-typed/src/test/scala/docs/akka/cluster/typed/BasicClusterExampleSpec.scala) { #config }
|
||||
: @@snip [BasicClusterExampleTest.java]($akka$/akka-cluster-typed/src/test/java/jdocs/akka/cluster/typed/BasicClusterExampleTest.java) { #cluster-imports }
|
||||
|
||||
Java
|
||||
: @@snip [BasicClusterExampleTest.java]($akka$/akka-cluster-typed/src/test/java/jdocs/akka/cluster/typed/BasicClusterExampleTest.java) { #cluster-imports }
|
||||
|
||||
## Cluster API extension
|
||||
|
||||
|
|
@ -39,6 +45,8 @@ The references are on the `Cluster` extension:
|
|||
Scala
|
||||
: @@snip [BasicClusterExampleSpec.scala]($akka$/akka-cluster-typed/src/test/scala/docs/akka/cluster/typed/BasicClusterExampleSpec.scala) { #cluster-create }
|
||||
|
||||
Java
|
||||
: @@snip [BasicClusterExampleTest.java]($akka$/akka-cluster-typed/src/test/java/jdocs/akka/cluster/typed/BasicClusterExampleTest.java) { #cluster-create }
|
||||
|
||||
The Cluster extensions gives you access to:
|
||||
|
||||
|
|
@ -54,11 +62,17 @@ If not using configuration to specify seeds joining the cluster can be done prog
|
|||
Scala
|
||||
: @@snip [BasicClusterExampleSpec.scala]($akka$/akka-cluster-typed/src/test/scala/docs/akka/cluster/typed/BasicClusterExampleSpec.scala) { #cluster-join }
|
||||
|
||||
Java
|
||||
: @@snip [BasicClusterExampleTest.java]($akka$/akka-cluster-typed/src/test/java/jdocs/akka/cluster/typed/BasicClusterExampleTest.java) { #cluster-join }
|
||||
|
||||
Leaving and downing are similar e.g.
|
||||
|
||||
Scala
|
||||
: @@snip [BasicClusterExampleSpec.scala]($akka$/akka-cluster-typed/src/test/scala/docs/akka/cluster/typed/BasicClusterExampleSpec.scala) { #cluster-leave }
|
||||
|
||||
Java
|
||||
: @@snip [BasicClusterExampleTest.java]($akka$/akka-cluster-typed/src/test/java/jdocs/akka/cluster/typed/BasicClusterExampleTest.java) { #cluster-leave }
|
||||
|
||||
### Cluster subscriptions
|
||||
|
||||
Cluster `subscriptions` can be used to receive messages when cluster state changes. For example, registering
|
||||
|
|
@ -70,11 +84,17 @@ This example subscribes with a `TestProbe` but in a real application it would be
|
|||
Scala
|
||||
: @@snip [BasicClusterExampleSpec.scala]($akka$/akka-cluster-typed/src/test/scala/docs/akka/cluster/typed/BasicClusterExampleSpec.scala) { #cluster-subscribe }
|
||||
|
||||
Java
|
||||
: @@snip [BasicClusterExampleTest.java]($akka$/akka-cluster-typed/src/test/java/jdocs/akka/cluster/typed/BasicClusterExampleTest.java) { #cluster-subscribe }
|
||||
|
||||
Then asking a node to leave:
|
||||
|
||||
Scala
|
||||
: @@snip [BasicClusterExampleSpec.scala]($akka$/akka-cluster-typed/src/test/scala/docs/akka/cluster/typed/BasicClusterExampleSpec.scala) { #cluster-leave-example }
|
||||
|
||||
Java
|
||||
: @@snip [BasicClusterExampleTest.java]($akka$/akka-cluster-typed/src/test/java/jdocs/akka/cluster/typed/BasicClusterExampleTest.java) { #cluster-leave-example }
|
||||
|
||||
## Serialization
|
||||
|
||||
See [serialization](https://doc.akka.io/docs/akka/current/scala/serialization.html) for how messages are sent between
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue