Codemountain, Paulo Suzart's Blog

Archive for the ‘RabbitMQ’ Category

Scala: Implicits, Options and Pattern Match to start with RabbitMQ

with 3 comments

update: link to code, now using sxr 0.2.1

Hi folks. Long time away! Confess I hadn’t had nice ideas to work on, but now I have a lot.

While implementing one of them (sort of Scala lib to use Actors as RabbitMQ Queue consumers over the Java API), I figured that my lib user shouldn’t explicitly instantiate or even interact with some original RabbitMQ Java API classes. This small piece of my construct is sufficient to show nice Scala features and functional programming style (the following is not the unique solution or the best way to do that, but funny and teaching).

To consume a Queue, we  first connect to the broker and get a Connection and a Channel. Using this ConnectionFactory constructor is useful to get a Connection. However, it takes a set of parameters wrapped in ConnectionParameters class.

So I decided follow these rules:

1. lib users must not instantiate ConnectionParameters directly

2. configuration must appear in the order: user, password, virtual host

3. configuration must take one line and be transparent to lib client

4. Ah! Users shouldn’t use common Scala Lists

How to make it happens in Scala? My answer was put some interesting features together such as Pattern Match, Options, Case Classes and Implicit Conversions.

Using a SBT aproach to declare project dependencies, We have the format:


val connParams = "guest" % "guest" % "/" //say a abstract val named connParams

An implicit function will help to convert a String to an object that takes a String value and the previous declared parameter as arguments. So we get  Param(“/, Param(“guest”, Param(“guest”, None))). Every time the user call the function % on a String, this implicit converter will instantiate a new Param.

The convertion function can be done as:

 implicit def s2Param(s : String) = Param(s, None)

Here a new element takes place, the Param class hasn’t a new keyword. That is a Case Class, a especial Class that help us with PatternMatch and  doesn’t need such a keyword.  And more, the parameters of a case class are “mapped to” attributes accessible by its own names:

case class Param(value : String, precending : Option[Param])

So, say a variable named x referencing Param, x allows x.value and x.precending without any extra declarations. 🙂

s2Param implicitly converts a String to a Param Instance to bring the function %. Let see the function %:


case class Param(value : String, preceding : Option[Param]) {

def % (value : String) = Param(value, Some(this))

}

For our surprise, we have one more element here, the Some –  in conjunction with None (see s2Param) – extends Option[+A]. Option is used to represent optional values, and here is a nice place to apply it. For example, the preceding Param of a Param is not required for the first in the chain.

None means no value, and Some means some value of the type of +A. Option has many other features not covered here.

Now, with the class designed, we can add another function (used strictly by the lib) to transform the parameter chain into the Rabbit ConnectionParameters:


def asConnectionParams = { 

 val (user, pass, vHost)  = this match {
   case Param(user, None) =>  (user, ConnectionParameters.DEFAULT_PASS, ConnectionParameters.DEFAULT_VHOST)
   case Param(pass, Some(Param(user, None))) => (user, pass, ConnectionParameters.DEFAULT_VHOST)
   case Param(vHost, Some(Param(pass, Some(Param(user, None))))) => (user, pass, vHost)
   case _=> error("ConnectionParameters configuration Failed")
 }

 //Returning ConnectionParametters
 new ConnectionParameters {
   setUsername(user)
   setPassword(pass)
   setVirtualHost(vHost)
 }

 }

asConnectionParams uses Pattern Match to declare the val user, pass and vHost matching the current instance of Param (supposed to be the las declared by the user) against some expected patterns. This Pattern Match carres about the default values to configure the ConnectionParametters instance returned. Any other Param combination not allowed will fall into case _, and a error is raised.

Now the Param class complete:


   case class Param(value : String, preceding : Option[Param]) {

      require(value != null)  

      def % (value : String) = Param(value, Some(this))

      def asConnectionParams = { 

        val (user, pass, vHost)  = this match {
           case Param(user, None) =>  (user, ConnectionParameters.DEFAULT_PASS, ConnectionParameters.DEFAULT_VHOST)
           case Param(pass, Some(Param(user, None))) => (user, pass, ConnectionParameters.DEFAULT_VHOST)
           case Param(vHost, Some(Param(pass, Some(Param(user, None))))) => (user, pass, vHost)
           case _=> error("ConnectionParameters configuration Failed")
          }

        //Returnin ConnectionParametters
        new ConnectionParameters {
        	setUsername(user)
        	setPassword(pass)
        	setVirtualHost(vHost)
        }

      }
    }

Usage:


val params = "guest" % "guest" % "/"

Thus, the super class can execute params asConnectionParams to grab the params required to get a Connection and a RabbitMQ channel.

To make things simple to test by yourself, you can find the code that runs stand alone here (with Scala X-Ray). Next time I promess a Scalatest or something like.

Thats it! Impressive? No! Useful? Maybe. Funny? For sure!!!

Stay up and see more POSTs about real RabbitMQ and Scala soon. It was an appetizer to play around with Scala.

Thanks for coming!

Anúncios

Written by paulosuzart

agosto 1, 2009 at 7:52 pm

Publicado em RabbitMQ, scala

Tagged with ,