Kotan Code 枯淡コード

In search of simple, elegant code

Menu Close

Dispatching Messages in Scala Without Conditional Statements

So far I have been having a lot of fun tinkering with an Akka IO socket server sitting atop an Actor System that can send and receive protocol buffer messages, ideally when communicating with a mobile client like iOS.

Up to this point, the samples that I have been using all have a single message type and I’ve been dispatching inbound messages to a single fixed Actor. That’s not realistic. I could be getting combat messages, communication messages, navigation messages, commerce/trade messages, and any number of other types of messages. I need to know what class to de-serialize those bytes into (this is why I put the message type as a number on the wire) and I need to know which actor to send that message to once I’ve received it.

The first thing I want is to be able to create a dispatcher map, which maps a message type to a tuple that I’ll be using to store the prototype of the message (the defaultInstance , which can be used to create new instances via mergeFrom) as well as a string that contains the actor path for the actor to which I ultimately want to send the message.

Here’s how I’ve defined my dispatcher map:


type Mergeable = { def mergeFrom(data: Array[Byte]): com.google.protobuf.GeneratedMessageLite }
val dispatchMap = Map(
  1 -> (ZombieSighting.defaultInstance.asInstanceOf[Mergeable], "akka://MyGame/user/ServerCore/DispatchTarget")
)

There’s a couple of interesting things going on here. First, I’m using a structural type to indicate that anything that has a mergeFrom method on it that produces a protobuf message is called a Mergeable and I can then pass stuff around as though Mergeable was an interface, even though the protobuf implementation for Scala that I have doesn’t treat it as such.

Next, I’m mapping an int (which I’ll replace with constants later) to a tuple containing the default instance of the corresponding message and the actor path of the actor to whom I will be dispatching the message.

Next comes the part that I struggled with a litte. I want to be able to dispatch the appropriate message to the appropriate actor but I don’t want to use any statements like if (messageType == 1) { … } , because that’s very un-Scala looking and not as functional as I would like. I know that I have access to the map function in Scala which is pretty powerful, and my first attempt at using it worked, but it wasn’t quite as pretty.

After getting some more advice from the amazing people on Stack Overflow, I converted most of my map and “get” (Option handling) code into a for comprehension. Below is my completed dispatchMessage method, which is part of an Akka IO Iteratee stream:

def dispatchMessage: IO.Iteratee[Unit] =
  repeat {
	for {
		(messageType, byteString) <- readMessage
	} yield {
		for (
			(protoMessage, actorPath) <- dispatchMap.get(messageType);
			theMessage = protoMessage.mergeFrom(byteString.toArray);
			targetActor = SocketServer.system.actorFor(actorPath) )
		yield
			targetActor ! theMessage
  }
}

And now when I get to line 11, I can simply send theMessage to targetActor and the dispatching has taken place and now my facade on top of my business logic is really, really thin and the only thing I need to do is maintain the dispatch map object whenever I add new message support to my application server.