Here is the code:

// Backend.scala package com.packt.akka.cluster import akka.actor.{Actor, ActorSystem, Props, RootActorPath} import akka.cluster.ClusterEvent.MemberUp import akka.cluster._ import com.packt.akka.commons._ import com.typesafe.config.ConfigFactory class Backend extends Actor { val cluster = Cluster(context.system) // subscribe to cluster changes, MemberUp // re-subscribe when restart override def preStart(): Unit = { cluster.subscribe(self, classOf[MemberUp]) } override def postStop(): Unit = { cluster.unsubscribe(self) } def receive = { case Add(num1, num2) => println(s"I'm a backend with path: ${self} and I received add operation.") println(s"Result is ${num1 + num2}") case MemberUp(member) => if(member.hasRole("frontend")){ context.actorSelection(RootActorPath(member.address) / "user" / "frontend") ! BackendRegistration } } } object Backend { def initiate(port: Int = 8991): Unit = { val config = ConfigFactory.parseString(s"akka.remote.netty.tcp.port=$port"). withFallback(ConfigFactory.load().getConfig("Backend")) val system = ActorSystem("ClusterSystem", config) val Backend = system.actorOf(Props[Backend], name = "Backend") } }

// ClusterApp.scala package com.packt.akka.cluster import akka.cluster._ import com.packt.akka.commons._ import akka.actor.{ Actor, ActorRef, ActorSystem, Props } object ClusterApp extends App { //initiate frontend node Frontend.initiate() //initiate three nodes from backend Backend.initiate(2552) Backend.initiate(2560) Backend.initiate(2561) Thread.sleep(10000) Frontend.getFrontend ! Add(2, 4) Frontend.getFrontend ! Add(2, 5) Frontend.getFrontend ! Add(2, 6) }

// Frontend.scala package com.packt.akka.cluster import akka.actor.{Actor, ActorRef, ActorSystem, Props, Terminated} import com.packt.akka.commons._ import com.typesafe.config.ConfigFactory import scala.util.Random class Frontend extends Actor { var backends = IndexedSeq.empty[ActorRef] def receive = { case Add if backends.isEmpty => println("Service unavailable, cluster doesn't have backend node.") case addOp: Add => println("Frontend: I'll forward add operation to backend node to handle it.") backends(Random.nextInt(backends.size)) ! addOp case BackendRegistration if !(backends.contains(sender())) => backends = backends :+ sender() context watch (sender()) case Terminated(a) => backends = backends.filterNot(_ == a) } } object Frontend { private var _frontend: ActorRef = _ def initiate() = { val config = ConfigFactory.load().getConfig("Frontend") val system = ActorSystem("ClusterSystem", config) _frontend = system.actorOf(Props[Frontend], name = "frontend") } def getFrontend = _frontend }

// application.conf Frontend { akka { actor { provider = "akka.cluster.ClusterActorRefProvider" } remote { log-remote-lifecycle-events = off netty.tcp { hostname = "127.0.0.1" port = 2551 } } cluster { roles = ["frontend"] seed-nodes = [ "akka.tcp://ClusterSystem@127.0.0.1:2551"] auto-down-unreachable-after = 10s } } } Backend { akka{ actor { provider = "akka.cluster.ClusterActorRefProvider" } remote { log-remote-lifecycle-events = off netty.tcp { hostname = "127.0.0.1" port = 0 } } cluster { roles = ["backend"] seed-nodes = [ "akka.tcp://ClusterSystem@127.0.0.1:2551"] auto-down-unreachable-after = 10s } } }

A few questions: