Thursday, June 11, 2009

Quotes with Actors Part II

This is where we'll be getting to the good part. For those of you are just now joining, the part one of the this series can be found here. Today I'm going to be talking about how to make those communicating actors cross JVM, cross machine, cross platform. By changing a our code base a bit, and make use of the RemoteActors, we can achieve our goal.

So let me give you a scenario. Let's say Steve has an extensive collection of video games quotes that he thought were generally entertaining, and his buddy Bill has an extensive collection B-movie Sci-Fi quotes he thought were entertaining. Steve read a few of Bill's random quotes; low and behold, even he was entertained! But here's the problem: Steve doesn't want to pollute his quote collection with Bill's quotes, but he want people to know about Bill's quotes, when people connect to his quote server.

In the last post we had a quote generator that would start an actor and loop away. Since we've established a client/server terminology, let's take that code and make it accessible over the wire.

import scala.actors.Actor._
import scala.actors.RemoteActor._
import java.util.Random

class QuoteServer(val port: Int, val name: Symbol) {
  lazy val quotes = ... // Load our quotes from the "store"

  val generator = actor {
    register(name, self)
    val rnd = new Random()
    loop {
      react {
        case Request() => {   
          val (who, said, from) = quotes(rnd.nextInt(quotes.size))
          sender ! Quote(from, who, said)

alive and register are methods on the object RemoteActor. One tells the actor to stay alive on a particular port, and the other registers the actor (in this case, self). That's basically it! Now we just need some code that kicks off that process to try it out.

scala> import calico.quotes.server.QuoteServer
import calico.quotes.server.QuoteServer

scala> import calico.quotes.client.Request
import calico.quotes.client.Request

scala> val server = new QuoteServer(9090, 'local)
server: calico.quotes.server.QuoteServer = calico.quotes.server.QuoteServer@1742c56

scala> server.generator !? Request()
res0: Any = Quote(Suikoden II,Luca Blight,"I've chopped hundreds,
 thousands of necks. I can do it with my eyes closed!")

Now I haven't demonstrated the remoteness yet, but it's all set up to work properly. The attentive would have noticed that the Request case class was moved to a client package. Some client some make a request. Remember, we are trying to make request over the wire. Sounds like it could be difficult right?

import scala.actors.remote.{Node, RemoteActor}

case class Request()
case class Quote(from: String, who: String, what: String)

class QuoteC(ip: String, port: Int, val name: Symbol) {
  private val remote =, port), name)

  def quote = { 
    remote !? (1000, Request()) match {
      case Some(Quote(from, who, what)) => what + "\n" + " - " + who + " from " + from
      case _ => "No quotes! Sorry... Is the server up?"

We changed a couple of things from out last implementation, most notably, the use of the RemoteActor. The !? operator is overloaded with ability to set a timeout. Since we're dealing with remote activity, we don't want a request attempt to block the process. Instead, we just give the user a nice generic error message, for simplicity's sake.

Without show some implementation code of this simple library, we can do something like this:

> java -jar quote_server.jar
Please enter a port and server name

> java - jar quote_server.jar 9073 video_games
Starting server 'video_games on port 9073...

Let's try to connect.

> java -jar quote_client.jar
Welcome to the quote REPL
> /connect 9073 video_games
Connecting to 'video_games server
> /quote
"I've chopped hundreds, thousands of necks. I can do 
 it with my eyes closed!"
- Luca Blight from Suikoden II
> /exit

I went ahead and threw the client implementation in a loop for kicks, but a quote server can act as a client to other quote servers with a small change.

case Request() => { 
   val (who, said, from) = quotes(rnd.nextInt(quotes.size))
      sender ! Quote(from, who, said)
case ServerList(ls) => sender ! ServerList(QuoteC.servers)

Now we have a way to return a server list from this server, so it's possible for a client to "hop" around from server to server, or a server to create categories with server lists, etc. The point is: a distributed quote server system can appear localized from a single client connection. This is exactly what we wanted from our example implementation.

Some things to note:

  • We could easily make and online interface by using the lift web framework.
  • Concurrency is achieved by one quote server being able to handle several concurrent request.
  • Going remote is easy!
  • There are several implementations one can make with our little library.

This was an example of how one make use of the RemoteActor's in their code. It may not be the best example, but it's a good place to start in understanding how it works.

-- Philip

No comments:

Post a Comment