In my previous post, I provided a simple “hello world” type application that utilizes the Akka Event Bus. In this sample, I showed how you can use a simple LookupClassification to classify the events being published. I glossed over this detail in the previous post to keep things simple, but the lookup classification does what its name implies – it does a lookup on the classifier (in my case, it was a string) and returns the subscribers for that classifier. There is no hierarchy involved. With this situation, if you subscribe to /zombies/Foo you will not see events published to /zombies.

In this blog post, I want to talk about how we can add hierarchical topic support to the event bus by switching from a lookup classification to a subchannel classification. In the subchannel classification system, I can subscribe to /zombies/Foo and I will receive events published to /zombies. Additionally, if I publish to /zombies/A and I am subscribed to /zombies/B I will not receive that event. This type of topic-based subscription should be familiar to those of you who have used service buses, messaging middleware, or the evil creature known as JMS.

So let’s get to it. First, I want to create my own subchannel classification. The beauty of this is that you can provide as little or as much logic as you like. In my case, I’m just going to do a standard string comparison to classify, and I am going to use “starts with” to identify a subchannel. I could get much more involved, even using regular expressions, if I wanted to.

class ZombieSightingSubclassEventBus extends ActorEventBus with SubchannelClassification {
  type Event = ZombieSightingEvent
  type Classifier = String

  protected def classify(event: Event): Classifier = event.topic

  protected def subclassification = new Subclassification[Classifier] {
    def isEqual(x: Classifier, y: Classifier) = x == y
    def isSubclass(x: Classifier, y: Classifier) = x.startsWith(y)
  }

  protected def publish(event: Event, subscriber: Subscriber): Unit = {
    subscriber ! event.sighting
  }
}

Now if I modify my previous blog post’s application object as follows, I’ll get some very robust behavior:

object ZombieTrackerApp extends App {

  val system = ActorSystem()
  //val eventBus = new ZombieSightingLookupEventBus
  val eventBus = new ZombieSightingSubclassEventBus

  val subscriber = system.actorOf(Props(new Actor {
    def receive = {
      case s:ZombieSighting => println(s"Spotted a zombie! ${s}")
    }
  }))

  val westCoastSightingHandler = system.actorOf(Props(new Actor {
  	def receive = {
		case s:ZombieSighting => println(s"West coast zombie ${s}!!")
	}
  }))

  val eastCoastSightingHandler = system.actorOf(Props(new Actor {
	def receive = {
		case s:ZombieSighting => println(s"East coast zombie ${s}!!")
	}
  }))

  eventBus.subscribe(subscriber, "/zombies")
  eventBus.subscribe(westCoastSightingHandler, "/zombies/WEST")
  eventBus.subscribe(eastCoastSightingHandler, "/zombies/EAST")

  eventBus.publish(ZombieSightingEvent("/zombies/WEST", ZombieSighting("FATZOMBIE1", 37.1234, 45.1234, 100.0)))
  eventBus.publish(ZombieSightingEvent("/zombies/EAST", ZombieSighting("SKINNYBOY", 30.1234, 50.1234, 12.0)))

  // And this one will NOT go off into deadletter like before ... this satisfies "startsWith" on the first subscriber
  eventBus.publish(ZombieSightingEvent("/zombies/foo/bar/baz", ZombieSighting("OTHERONE", 35.0, 42.5, 50.0)))
  system.shutdown
}

Here I’ve decided to create one subscriber that listens for west coast zombies and another one that listens for east coast zombies. In the sample above I’ve used two anonymous actor classes but I could very easily have used instances of the same actor class that were primed with different constructor props (such as the region to which they were bound). As I mentioned above, the old listener should receive all events on /zombies, but also my new east and west subscribers should also receive /zombies messages in addition to their own regional localized events.

Here’s what the program run output looks like:

[info] Running ZombieTrackerApp 
Spotted a zombie! ZombieSighting(FATZOMBIE1,37.1234,45.1234,100.0)
West coast zombie ZombieSighting(FATZOMBIE1,37.1234,45.1234,100.0)!!
East coast zombie ZombieSighting(SKINNYBOY,30.1234,50.1234,12.0)!!
Spotted a zombie! ZombieSighting(SKINNYBOY,30.1234,50.1234,12.0)
Spotted a zombie! ZombieSighting(OTHERONE,35.0,42.5,50.0)
[success] Total time: 6 s, completed Feb 12, 2014 8:39:09 PM

And so this is hopefully enough to whet your appetite on the Akka Event Bus. And by “whet your appetite” I mean that in the most drug-pusher way possible. The first one’s free, now enjoy your new addiction to the event bus 🙂