Akka remoting

Now, we can implement the final piece of the puzzle—the shop boy and the sendBoy function that we left uncovered until now. The Boy does not belong to the Bakery. The Manager will need to send the Boy to the grocery Store, which is represented by another actor system.

In order to do so, we'll rely on Akka's location transparency and remote capabilities. First, the manager will deploy a boy actor to the remote system. The deployed actor will get a reference to the Seller actor in the store so that it can get groceries as and when required.

There are two ways to use remoting in Akka—either by using actor lookup or actor creation. Both are used in the same way that we did locally until now, that is, by calling actorSelection and actorOf, respectively.

Here, we will demonstrate both ways that the manager will look up a seller from whom the boy should get groceries (imagine this seller is working with the bakery on a prepaid basis) and then require the boy to interact with this specific actor.

Before we dive into the code, we need to augment the setup of our application. Remoting is a separate dependency in Akka which we will put into the build.sbt:

libraryDependencies += "com.typesafe.akka" %% "akka-remote" % akkaVersion

Then, we need to replace the local actor provider with the remote one and configure the network settings in application.conf:

akka {
actor.provider = remote
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 2552
}
}
}

The same configuration, but with a necessarily different port, is provided for the second actor system that's representing the grocery store. This is done by reusing the application.conf via inclusion and redefining the TCP port:

include "application"
akka.remote.netty.tcp.port = 2553

Then, we need to define the grocery store:

import akka.actor._
import com.example.Manager.ShoppingList
import com.example.Mixer.Groceries
import com.typesafe.config.ConfigFactory

object Store extends App {
val store = ActorSystem("Store", ConfigFactory.load("grocery.conf"))

val seller = store.actorOf(Props(new Actor {
override def receive: Receive = {
case s: ShoppingList =>
ShoppingList.unapply(s).map(Groceries.tupled).foreach(sender() ! _)
}
}), "Seller")
}

We can't use the default configuration as it is already taken by the bakery system, so we need to load a custom grocery.conf by using ConfigFactory.load. Next, we need to create an anonymous (but named!) actor whose sole responsibility is to return groceries, as described by the ShoppingList, to the sender.

Finally, we're ready to implement the sendBoy function in the Manager:

private def sendBoy: ActorRef = {
val store = "akka.tcp://[email protected]:2553"
val seller = context.actorSelection(s"$store/user/Seller")
context.actorOf(Boy.props(seller))
}

First, we must define the address of the grocery store. Then, we need to look up a seller by using its address on the remote system. Akka's documentation specifies the following pattern for remote actor lookup:

akka.<protocol>://<actor system name>@<hostname>:<port>/<actor path>

We'll take a look at this template and especially the actor path in a moment.

Then, we need to create a boy by using our usual actorOf method. To tell Akka to deploy this actor remotely, we need to put the following configuration into application.conf:

akka.actor.deployment {
/Manager/Boy {
remote = "akka.tcp://[email protected]:2553"
}
}

This instructs Akka not to instantiate a local actor but to contact the remote daemon running in the actor system with the name Store with a network address of 127.0.0.1:2553 and to tell this daemon to create a remote actor.

We could achieve the same result without extending configuration by providing deployment configuration directly in the code:

val storeAddress = AddressFromURIString(s"$store")
val boyProps = Boy.props(seller).withDeploy(Deploy(scope = RemoteScope(storeAddress)))
context.actorOf(boyProps)

This snippet creates a store address from the string we defined earlier and explicitly tells Akka to use it while creating the actor.

The implementation of Boy is trivial now:

object Boy {
def props(seller: ActorSelection): Props = Props(classOf[Boy], seller)
}

class Boy(seller: ActorSelection) extends Actor {
override def receive = {
case s: ShoppingList =>
seller forward s
self ! PoisonPill
}
}

The Boy constructor's takes a parameter of type ActorSelection, which is the result of the remote lookup that was done by the Manager previously. By receiving a ShoppingList, our implementation uses forward to, well, forward the message directly to the seller. Because of this forwarding, the seller will receive a message with the original actor (who is a manager) as a sender. 

Finally, we will take into the account that the boy was created by the manager just for the purpose of going shopping once, and we need to clean up the resources. This can be done by the manager actor, but we prefer self-cleanup here. The Boy sends himself a PoisonPill immediately after forwarding the original message and terminates.

Now that we're done defining all of the inhabitants of our bakery, we can wire them together and bake some cookies:

object Bakery extends App {
val bakery = ActorSystem("Bakery")
val cook: ActorRef = bakery.actorOf(Props[Cook], "Cook")
val chef: ActorRef = bakery.actorOf(Props[Chef], "Chef")
val oven: ActorRef = bakery.actorOf(Oven.props(12), "Oven")
val baker: ActorRef = bakery.actorOf(Baker.props(oven), "Baker")
val manager: ActorRef = bakery.actorOf(Manager.props(chef, cook, baker), "Manager")
}

Before we run our app and enjoy some cookies, let's take a break from coding and look at the actor path we saw in the remote actor lookup pattern.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.74.25