Rapid Application Prototyping with Groovy and RabbitMQ

J. Brisbin's blog on the Virtual Private Cloud, RabbitMQ, and Web 2.x – Asynchronous, messaging-oriented applications are pretty hard to visualize. In writing the vcloud session manager, I was finding I couldn't really see in my head what was happening when I dumped a message into queue "X", or why it wasn't showing up in consumer "Y". I wrote the Groovy DSL for RabbitMQ to help me with this. Now I want to show you a more in-depth example of how I'm using the DSL to rapidly prototype applications and help me see, in concrete terms, how exchanges, queues, and messages are wired together. One of the hurdles I'm having to overcome with all these virtual cloud components (a dozen or more virtual machines that do various things) is keeping configuration files in sync. At the moment I'm using a combination of cron jobs and shared NFS mounts. Neither of these is what I'm really after, though. Sure, they work, but they're not very dynamic and they suffer from single points of failure. To get away from that, I'm writing some utilities (some in Python, some in Java) to keep these configuration files in sync. To visualize how this application is going to work, I'm prototyping it in Groovy, using the RabbitMQ DSL. First Steps One of the first things I know I need is a membership manager. In my prototype, I'll use an in-memory array and a very simple "fanout" exchange to listen for membership events:

def members = []
mq.exchange name: "vcloud.config.membership", type: "fanout"

When we get a membership message, we'll push the node name onto the members stack:

// Manage membership
queue(name: "master.membership", routingKey: "") {

  consume onmessage: {msg ->
    println "Received ${msg.envelope.routingKey} event " +
            "from ${msg.bodyAsString}..."
    if(msg.envelope.routingKey == "join") {
      members << msg.bodyAsString
      println "Members: ${members.toString()}"
      return msg.envelope.routingKey != "exit"
    }
  }

}

Now, I'll publish a couple membership messages to this queue to see my "println" messages in the console, and see the membership list change. The full Groovy DSL code to do this is:

println "Creating membership exchange"
mq.exchange(name: "vcloud.config.membership", type: "fanout") {

  // Manage membership
  queue(name: "master.membership", routingKey: "") {
    consume onmessage: {msg ->
      println "Received ${msg.envelope.routingKey} event " +
              "from ${msg.bodyAsString}..."
      if(msg.envelope.routingKey == "join") {
        members << msg.bodyAsString
        println "Members: ${members.toString()}"
      }
      return msg.envelope.routingKey != "exit"
    }
  }

  // Join two nodes
  publish routingKey: "join", body: "dev1"
  publish routingKey: "join", body: "dev2"

}

Running this results in the following console output:

Creating membership exchange
Received join event from dev1...
Members: [dev1]
Received join event from dev2...
Members: [dev1, dev2]

This is very helpful because it lets me quickly comprehend how my queues exist under specific exchanges. It lets me visually connect message publishing to message consuming. If I wanted to, I could now take this Groovy code and code a real application against it. Of course, there's nothing stopping me from simply writing the "real" application entirely in Groovy, either! More Consumers Now that I've got a skeleton upon which to prototype my membership manager, I can create a couple queues that simulate the actual nodes that will be joining the cloud and listening for configuration file change events. This exchange will be a topic exchange, so I can send messages to the entire group:

// Listen for incoming config events on test node 1
queue(name: "dev1", routingKey: "dev1") {
  consume tag: "dev1.config", onmessage: {msg ->
    println " ***** INCOMING: ${msg.toString()}"
    if(msg.properties.headers["key"]) {
      println "(dev1) Config change for key: ${msg.properties.headers['key']}"

      // Also let node 2 know about this
      send("vcloud.config.events", "dev2", ["key": "firstconfig"], msg.body)
    }
    return false
  }
}

This dumps the incoming message to stdout and forwards the message on to a second node, which has a virtually identical consumer.

What’s wrong? The new clean desk test
Join the discussion
Be the first to comment on this article. Our Commenting Policies