diff --git a/akka-cluster-tools/src/main/scala/akka/cluster/client/ClusterClient.scala b/akka-cluster-tools/src/main/scala/akka/cluster/client/ClusterClient.scala index 21e2e15af9..092af156ad 100644 --- a/akka-cluster-tools/src/main/scala/akka/cluster/client/ClusterClient.scala +++ b/akka-cluster-tools/src/main/scala/akka/cluster/client/ClusterClient.scala @@ -5,8 +5,10 @@ package akka.cluster.client import java.net.URLEncoder + import scala.collection.immutable import scala.concurrent.duration._ + import akka.actor.Actor import akka.actor.ActorIdentity import akka.actor.ActorLogging @@ -41,6 +43,9 @@ import akka.util.ccompat._ import scala.collection.immutable.{ HashMap, HashSet } @ccompatUsedUntil213 +@deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") object ClusterClientSettings { /** @@ -109,6 +114,9 @@ object ClusterClientSettings { * to watch it from another actor and possibly acquire a new list of initialContacts from some * external service registry */ +@deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") final class ClusterClientSettings( val initialContacts: Set[ActorPath], val establishingGetContactsInterval: FiniteDuration, @@ -271,15 +279,24 @@ final case class ContactPoints(contactPoints: Set[ActorPath]) { contactPoints.asJava } +@deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") object ClusterClient { /** * Scala API: Factory method for `ClusterClient` [[akka.actor.Props]]. */ + @deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") def props(settings: ClusterClientSettings): Props = Props(new ClusterClient(settings)).withDeploy(Deploy.local) @SerialVersionUID(1L) + @deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") final case class Send(path: String, msg: Any, localAffinity: Boolean) { /** @@ -288,8 +305,15 @@ object ClusterClient { def this(path: String, msg: Any) = this(path, msg, localAffinity = false) } @SerialVersionUID(1L) + @deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") final case class SendToAll(path: String, msg: Any) + @SerialVersionUID(1L) + @deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") final case class Publish(topic: String, msg: Any) /** @@ -344,6 +368,9 @@ object ClusterClient { * Note that this is a best effort implementation: messages can always be lost due to the distributed * nature of the actors involved. */ +@deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") final class ClusterClient(settings: ClusterClientSettings) extends Actor with ActorLogging { import ClusterClient._ @@ -525,6 +552,9 @@ final class ClusterClient(settings: ClusterClientSettings) extends Actor with Ac } } +@deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") object ClusterClientReceptionist extends ExtensionId[ClusterClientReceptionist] with ExtensionIdProvider { override def get(system: ActorSystem): ClusterClientReceptionist = super.get(system) @@ -539,6 +569,9 @@ object ClusterClientReceptionist extends ExtensionId[ClusterClientReceptionist] * with settings defined in config section `akka.cluster.client.receptionist`. * The [[akka.cluster.pubsub.DistributedPubSubMediator]] is started by the [[akka.cluster.pubsub.DistributedPubSub]] extension. */ +@deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") final class ClusterClientReceptionist(system: ExtendedActorSystem) extends Extension { private val config = system.settings.config.getConfig("akka.cluster.client.receptionist") @@ -614,6 +647,9 @@ final class ClusterClientReceptionist(system: ExtendedActorSystem) extends Exten receptionist } +@deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") object ClusterReceptionistSettings { /** @@ -663,6 +699,9 @@ object ClusterReceptionistSettings { * @param responseTunnelReceiveTimeout The actor that tunnel response messages to the * client will be stopped after this time of inactivity. */ +@deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") final class ClusterReceptionistSettings( val role: Option[String], val numberOfContacts: Int, @@ -822,6 +861,9 @@ final case class ClusterClients(clusterClients: Set[ActorRef]) { clusterClients.asJava } +@deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") object ClusterReceptionist { /** @@ -897,6 +939,9 @@ object ClusterReceptionist { * the client is supposed to communicate directly to the actor in the cluster. * */ +@deprecated( + "Use Akka gRPC instead, see https://doc.akka.io/docs/akka/2.6/cluster-client.html#migration-to-akka-grpc", + since = "2.6.0") final class ClusterReceptionist(pubSubMediator: ActorRef, settings: ClusterReceptionistSettings) extends Actor with ActorLogging { diff --git a/akka-cluster-tools/src/main/scala/akka/cluster/client/protobuf/ClusterClientMessageSerializer.scala b/akka-cluster-tools/src/main/scala/akka/cluster/client/protobuf/ClusterClientMessageSerializer.scala index d5bd9f8cda..4d9ba33074 100644 --- a/akka-cluster-tools/src/main/scala/akka/cluster/client/protobuf/ClusterClientMessageSerializer.scala +++ b/akka-cluster-tools/src/main/scala/akka/cluster/client/protobuf/ClusterClientMessageSerializer.scala @@ -12,9 +12,12 @@ import akka.cluster.client.ClusterReceptionist import akka.cluster.client.protobuf.msg.{ ClusterClientMessages => cm } import java.io.NotSerializableException +import com.github.ghik.silencer.silent + /** * INTERNAL API: Serializer of ClusterClient messages. */ +@silent("deprecated") private[akka] class ClusterClientMessageSerializer(val system: ExtendedActorSystem) extends SerializerWithStringManifest with BaseSerializer { diff --git a/akka-cluster-tools/src/multi-jvm/scala/akka/cluster/client/ClusterClientHandoverSpec.scala b/akka-cluster-tools/src/multi-jvm/scala/akka/cluster/client/ClusterClientHandoverSpec.scala index a20d9eb3ac..6fb3221456 100644 --- a/akka-cluster-tools/src/multi-jvm/scala/akka/cluster/client/ClusterClientHandoverSpec.scala +++ b/akka-cluster-tools/src/multi-jvm/scala/akka/cluster/client/ClusterClientHandoverSpec.scala @@ -10,9 +10,10 @@ import akka.remote.testconductor.RoleName import akka.remote.testkit.{ MultiNodeConfig, MultiNodeSpec, STMultiNodeSpec } import akka.testkit.{ ImplicitSender, TestActors } import com.typesafe.config.ConfigFactory - import scala.concurrent.duration._ +import com.github.ghik.silencer.silent + object ClusterClientHandoverSpec extends MultiNodeConfig { val client = role("client") val first = role("first") @@ -36,6 +37,7 @@ class ClusterClientHandoverSpecMultiJvmNode1 extends ClusterClientHandoverSpec class ClusterClientHandoverSpecMultiJvmNode2 extends ClusterClientHandoverSpec class ClusterClientHandoverSpecMultiJvmNode3 extends ClusterClientHandoverSpec +@silent("deprecated") class ClusterClientHandoverSpec extends MultiNodeSpec(ClusterClientHandoverSpec) with STMultiNodeSpec diff --git a/akka-cluster-tools/src/multi-jvm/scala/akka/cluster/client/ClusterClientSpec.scala b/akka-cluster-tools/src/multi-jvm/scala/akka/cluster/client/ClusterClientSpec.scala index bdf601a867..1f04b2a43e 100644 --- a/akka-cluster-tools/src/multi-jvm/scala/akka/cluster/client/ClusterClientSpec.scala +++ b/akka-cluster-tools/src/multi-jvm/scala/akka/cluster/client/ClusterClientSpec.scala @@ -6,6 +6,7 @@ package akka.cluster.client import language.postfixOps import scala.concurrent.duration._ + import com.typesafe.config.ConfigFactory import akka.actor.{ Actor, @@ -29,9 +30,10 @@ import akka.cluster.pubsub._ import akka.remote.transport.ThrottlerTransportAdapter.Direction import akka.util.Timeout import akka.util.unused - import scala.concurrent.Await +import com.github.ghik.silencer.silent + object ClusterClientSpec extends MultiNodeConfig { val client = role("client") val first = role("first") @@ -160,6 +162,7 @@ class ClusterClientMultiJvmNode3 extends ClusterClientSpec class ClusterClientMultiJvmNode4 extends ClusterClientSpec class ClusterClientMultiJvmNode5 extends ClusterClientSpec +@silent("deprecated") class ClusterClientSpec extends MultiNodeSpec(ClusterClientSpec) with STMultiNodeSpec with ImplicitSender { import ClusterClientSpec._ diff --git a/akka-cluster-tools/src/multi-jvm/scala/akka/cluster/client/ClusterClientStopSpec.scala b/akka-cluster-tools/src/multi-jvm/scala/akka/cluster/client/ClusterClientStopSpec.scala index e869f21322..b2b1b15aec 100644 --- a/akka-cluster-tools/src/multi-jvm/scala/akka/cluster/client/ClusterClientStopSpec.scala +++ b/akka-cluster-tools/src/multi-jvm/scala/akka/cluster/client/ClusterClientStopSpec.scala @@ -14,6 +14,8 @@ import com.typesafe.config.ConfigFactory import scala.concurrent.Await import scala.concurrent.duration._ +import com.github.ghik.silencer.silent + object ClusterClientStopSpec extends MultiNodeConfig { val client = role("client") val first = role("first") @@ -43,6 +45,7 @@ class ClusterClientStopMultiJvmNode1 extends ClusterClientStopSpec class ClusterClientStopMultiJvmNode2 extends ClusterClientStopSpec class ClusterClientStopMultiJvmNode3 extends ClusterClientStopSpec +@silent("deprecated") class ClusterClientStopSpec extends MultiNodeSpec(ClusterClientStopSpec) with STMultiNodeSpec with ImplicitSender { import ClusterClientStopSpec._ diff --git a/akka-cluster-tools/src/test/scala/akka/cluster/client/protobuf/ClusterClientMessageSerializerSpec.scala b/akka-cluster-tools/src/test/scala/akka/cluster/client/protobuf/ClusterClientMessageSerializerSpec.scala index 1e6f34f9cd..1e0d683b01 100644 --- a/akka-cluster-tools/src/test/scala/akka/cluster/client/protobuf/ClusterClientMessageSerializerSpec.scala +++ b/akka-cluster-tools/src/test/scala/akka/cluster/client/protobuf/ClusterClientMessageSerializerSpec.scala @@ -7,7 +7,9 @@ package akka.cluster.client.protobuf import akka.actor.ExtendedActorSystem import akka.testkit.AkkaSpec import akka.cluster.client.ClusterReceptionist.Internal._ +import com.github.ghik.silencer.silent +@silent("deprecated") class ClusterClientMessageSerializerSpec extends AkkaSpec { val serializer = new ClusterClientMessageSerializer(system.asInstanceOf[ExtendedActorSystem]) diff --git a/akka-docs/src/main/paradox/cluster-client.md b/akka-docs/src/main/paradox/cluster-client.md index de72d531e0..efe69c6e73 100644 --- a/akka-docs/src/main/paradox/cluster-client.md +++ b/akka-docs/src/main/paradox/cluster-client.md @@ -1,7 +1,13 @@ # Classic Cluster Client +@@@ warning + +Cluster Client is deprecated in favor of using [Akka gRPC](https://doc.akka.io/docs/akka-grpc/current/index.html). +It is not advised to build new applications with Cluster Client, and existing users @ref[should migrate](#migration-to-akka-grpc). + +@@@ + @@include[includes.md](includes.md) { #actor-api } -Instead of the cluster client we recommend Akka gRPC (FIXME https://github.com/akka/akka/issues/26175) ## Dependency @@ -219,3 +225,70 @@ This can be useful when initial contacts are provided from some kind of service are entirely dynamic and the entire cluster might shut down or crash, be restarted on new addresses. Since the client will be stopped in that case a monitoring actor can watch it and upon `Terminate` a new set of initial contacts can be fetched and a new cluster client started. + +## Migration to Akka gRPC + +Cluster Client is deprecated and it is not advised to build new applications with Cluster Client. +We recommend using [Akka gRPC](https://doc.akka.io/docs/akka-grpc/current/index.html) over using Cluster Client. + +Cluster Client is convenient to use if you have Akka actors on both client and server side, but +a more decoupled, and therefore better, solution would be to use Akka gRPC and define +application specific protocol buffer messages and gRPC service calls. The benefits are: + +* Improved security by using TLS for gRPC (HTTP/2) versus exposing Akka Remoting outside the Akka Cluster +* Easier to update clients and servers independent of each other +* Improved protocol definition between client and server +* Usage of [Akka gRPC Service Discovery](https://doc.akka.io/docs/akka-grpc/current/client/configuration.html#using-akka-discovery-for-endpoint-discovery) +* Clients do not need to use Akka +* See also [gRPC versus Akka Remoting](https://doc.akka.io/docs/akka-grpc/current/whygrpc.html#grpc-vs-akka-remoting) + +Existing users of Cluster Client may migrate directly to Akka gRPC, or via a migration +step that requires less re-write of the application. That migration step is described below, but we recommend +new applications use Akka gRPC. + +An example is provided to illustrate an approach to migrate from the deprecated Cluster Client to Akka gRPC, +with minimal changes to your existing code. The example is intended to be copied and adjusted to your needs. +It will not be provided as a published artifact. + +* [akka-samples/akka-sample-cluster-cluster-client-grpc-scala](https://github.com/akka/akka-samples/tree/2.6/akka-sample-cluster-cluster-client-grpc-scala) implemented in Scala +* [akka-samples/akka-sample-cluster-cluster-client-grpc-java](https://github.com/akka/akka-samples/tree/2.6/akka-sample-cluster-cluster-client-grpc-java) implemented in Java + +The example is still using an actor on the client side to have an API that is very close +to the original Cluster Client. The messages this actor can handle correspond to the +@ref:[Distributed Pub Sub](distributed-pub-sub.md) messages on the server side, such as +`ClusterClient.Send` and `ClusterClient.Publish`. + +The `ClusterClient` actor delegates those messages to the gRPC client, and on the +server side those are translated and delegated to the destination actors that +are registered via the `ClusterClientReceptionist` in the same way as in the original. + +Akka gRPC is used as the transport for the messages between client and server, instead of Akka Remoting. + +The application specific messages are wrapped and serialized with Akka Serialization, +which means that care must be taken to keep wire compatibility when changing any messages used +between the client and server. The Akka configuration of Akka serializers must be the same (or +being compatible) on client and server. + +### Differences + +Aside from the underlying implementation using gRPC instead of Actor messages +and Akka Remoting it's worth pointing out the following differences between +the Cluster Client and the example emulating Cluster Client with Akka gRPC as +transport. + +#### Single request-reply + +For request-reply interactions when there is only one reply message for each request +it is more efficient to use the `ClusterClient.AskSend` message instead of +`ClusterClient.Send` as illustrated in the example. Then it doesn't have to +setup a full bidirectional gRPC stream for each request but can use the @scala[`Future`]@java[`CompletionStage`] +based API. + +#### Initial contact points + +Instead of configured initial contact points the [Akka gRPC Service Discovery](https://doc.akka.io/docs/akka-grpc/current/client/configuration.html#using-akka-discovery-for-endpoint-discovery) can be used. + +#### Failure detection + +Heartbeat messages and failure detection of the connections have been removed +since that should be handled by the gRPC connections. diff --git a/akka-docs/src/main/paradox/project/migration-guide-2.5.x-2.6.x.md b/akka-docs/src/main/paradox/project/migration-guide-2.5.x-2.6.x.md index bdc7c9b0b1..86a3c0d2a6 100644 --- a/akka-docs/src/main/paradox/project/migration-guide-2.5.x-2.6.x.md +++ b/akka-docs/src/main/paradox/project/migration-guide-2.5.x-2.6.x.md @@ -210,6 +210,11 @@ published, but the transitive dependency to `akka-protobuf` has been removed. Akka is now using Protobuf version 3.9.0 for serialization of messages defined by Akka. +### Cluster Client + +Cluster client has been deprecated as of 2.6 in favor of [Akka gRPC](https://doc.akka.io/docs/akka-grpc/current/index.html). +It is not advised to build new applications with Cluster client, and existing users @ref[should migrate to Akka gRPC](../cluster-client.md#migration-to-akka-grpc). + ## Java Serialization Java serialization is known to be slow and [prone to attacks](https://community.hpe.com/t5/Security-Research/The-perils-of-Java-deserialization/ba-p/6838995) @@ -499,11 +504,11 @@ made before finalizing the APIs. Compared to Akka 2.5.x the source incompatible ### System global Materializer provided A default materializer is now provided out of the box. For the Java API just pass `system` when running streams, -for Scala an implicit materializer is provided if there is an implicit `ActorSystem` available. This avoids leaking +for Scala an implicit materializer is provided if there is an implicit `ActorSystem` available. This avoids leaking materializers and simplifies most stream use cases somewhat. -Having a default materializer available means that most, if not all, usages of Java `ActorMaterializer.create()` -and Scala `implicit val materializer = ActorMaterializer()` should be removed. +Having a default materializer available means that most, if not all, usages of Java `ActorMaterializer.create()` +and Scala `implicit val materializer = ActorMaterializer()` should be removed. Details about the stream materializer can be found in [Actor Materializer Lifecycle](../stream/stream-flows-and-basics.md#actor-materializer-lifecycle)