(Quick Reference)

2 Configuration - Reference Documentation

Authors: Jeff Brown, Peter Ledbrook

Version: 1.0.0

2 Configuration

The plugin supports a number of configuration options which all may be expressed in grails-app/conf/Config.groovy. A basic configuration might look something like this:

// grails-app/conf/Config.groovy
rabbitmq {
    connectionfactory {
        username = 'guest'
        password = 'guest'
        hostname = 'localhost'
    }
}

Those are settings which are necessary in order for the plugin to be able to connect to and communicate with a RabbitMQ server.

Following is a list of other configuration settings supported by the plugin.

Configuration PropertyDescriptionDefault
rabbitmq.connectionfactory.usernameThe user name for connection to the server(none)
rabbitmq.connectionfactory.passwordThe password for connection to the server(none)
rabbitmq.connectionfactory.hostnameThe host name of the server(none)
rabbitmq.connectionfactory.virtualHostThe name of the virtual host to connect to'/'
rabbitmq.connectionfactory.channelCacheSizeThe connection channel cache size10
rabbitmq.concurrentConsumersThe number of concurrent consumers to create per message handler. Raising the number is recommended in order to scale the consumption of messages coming in from a queue. Note that ordering guarantees are lost when multiple consumers are registered.1
rabbitmq.disableListeningDisables all service listeners so that they won't receive any messages.false
rabbitmq.retryPolicy.maxAttemptsSets the maximum number of retries for failed message deliveries0

2.1 Configuring Queues

Queues must be declared in the RabbitMQ server before consumers can be associated with those queues and before messages may be sent to those queues. If the Grails application will be sending messages to or receiving messages from queues that may not already be declared in the RabbitMQ server, the application needs to declare those queues up front. One way to do that is to add beans to the Spring application context of type org.springframework.amqp.core.Queue. That might look something like this:

// grails-app/conf/spring/resources.groovy
beans = {
    myQueue(org.springframework.amqp.core.Queue, 'myQueueName') 
    myOtherQueue(org.springframework.amqp.core.Queue, 'myOtherQueueName') {
        autoDelete = false
        durable = true
        exclusive = false
        arguments = [arg1: 'val1', arg2: 'val2']
    }
}

The plugin also supports a DSL for describing these queues. This DSL is expressed in grails-app/conf/Config.groovy. The code below configures the same queues as the previous code sample.

// grails-app/conf/Config.groovy
rabbitmq {
    connectionfactory {
        username = 'guest'
        password = 'guest'
        hostname = 'localhost'
    }
    queues = {
        myQueueName()
        myOtherQueueName autoDelete: false, durable: true, exclusive: false, arguments: [arg1: 'val1', arg2: 'val2']
    }
}

With both techniques, the autoDelete, durable and exclusive attributes default to false and the arguments attribute defaults to null.

So what do those queue options mean?

OptionDescription
autoDeleteIf true, the queue will be removed from the broker when there are no more clients attached to it. Note that this doesn't take effect until after at least one client connects to the queue.
durableIf true, the queue will survive a restart of the broker.
exclusiveOnly the client that created the queue can connect to it.

One final thing: when you declare a standalone queue like this, it automatically gets bound to the broker's default exchange, which has an implicit name of '', i.e. the empty string. You can easily send messages to this queue via the rabbitSend method.

2.2 Configuring Exchanges

Queues are the foundation of consuming messages, but what if you want to send messages? In AMQP, you send messages to an exchange and the exchange then routes those messages to the appropriate queues based on something called a binding. The key to setting up complex messaging systems lies in configuring these exchanges and queues appropriately.

Declaring an exchange

Let's start with an example of how to set up a simple exchange (with no queues):

rabbitmq {
    connectionFactory {
        …
    }
    queues = {
        exchange name: 'my.topic', type: topic
    }
}

As you can probably guess, this will create a topic exchange with the name 'my.topic'. There are two things to note at this point:

  1. the name and type are required
  2. the type value is not a string literal

So what types are available to you?

TypeDescription
directAn exchange that only routes messages that are bound to it with a key that matches the routing key of the message exactly. Typically this exchange is used for point-to-point messaging and the routing key is the queue name.
fanoutSends messages to all queues bound to it. It basically does a broadcast.
topicSimilar to the fanout exchange, this routes messages to the queues bound to it, but only queues whose binding matches a message's routing key will receive that message. Wildcards are supported in the binding.
headersSimilar to topic, but messages can be filtered by other any message header, not just the routing key.

The exchange declaration also supports a couple of extra options that should be familiar from the queue declarations:

OptionDescription
autoDeleteIf true, the exchange will be removed from the broker when there are no more queues bound to it. Note that this doesn't take effect until at least one queue is bound to the exchange.
durableIf true, the exchange will survive a restart of the broker.

With the above syntax, it is up to you to bind queues to the exchange via another AMQP client or via the RabbitMQ management interface. In other words, this is most suitable if your Grails application is purely a publisher of messages and not a consumer (or at least not a consumer of 'my.topic' messages).

What if you want to create queues and automatically bind them to the exchange? Don't worry, that's supported by the configuration DSL too.

Binding queues to exchanges

An exchange on its own isn't particularly useful, but we can easily bind queues to it by declaring them as nested entries:

rabbitmq {
    connectionFactory {
        …
    }
    queues = {
        exchange name: 'my.topic', type: topic, durable: false, {
            foo durable: true, binding: 'shares.#'
            bar durable: false, autoDelete: true, binding: 'shares.nyse.?'
        }
    }
}

In the example above, we bind two queues ('foo' and 'bar') to the exchange 'my.topic'. Since this is a topic exchange, we can use a binding key to filter which messages go from 'my.topic' to each queue. So in this case, only messages with a routing key beginning with 'shares.' will end up on the 'foo' queue. 'bar' will only receive messages whose routing key begins with 'shares.nyse.'.

This approach isn't limited to topic exchanges: you can automatically bind queues to any exchange type. There are a few things to bear in mind though:

  1. the default binding for direct exchanges is the queue name (unless this is explicitly overridden by a 'binding' option);
  2. the 'binding' is ignored for fanout exchanges; and
  3. the headers exchange requires a map of message header names and values for its binding.

RabbitMQ has several built-in exchanges with names of the form 'amq.*', for example 'amq.direct'. If you want to bind to these, you currently have to declare them with the correct attributes, i.e.

exchange name: "amq.direct", type: direct, durable: true, autoDelete: false

As you can imagine, these few building blocks allow you to configure some pretty complex messaging systems with very little effort. You can tailor the messaging system to your needs rather than tailor your applications to the messaging system.

2.3 Advanced Configuration

When you need fine-grained control over your service listeners, you can tap into the power of Spring. Since each service listener is implemented as a set of Spring beans, you can use Grails' bean property override mechanism to provide your own low-level settings.

So how are these beans set up? If a service has either a rabbitQueue or rabbitSubscribe property, then you will have these beans:

As an example, let's say you have a MessageStoreService like so:

class MessageStoreService {
    static rabbitSubscribe = [exchange: "amq.topic", routingKey: "logs.#"]
    …
}

You can then customise things like the number of concurrent consumers, whether the channel is transacted, what the prefetch count should be, and more! Simply add code like this to your runtime configuration (Config.groovy):

beans {
    messageStoreService_MessageListenerContainer {
        channelTransacted = false
        concurrentConsumers = 10
        prefetchCount = 5
        queueNames = ["q1", "q2"] as String[]
    }

messageStoreServiceRabbitAdapter { encoding = "UTF-8" responseRoutingKey = "replyQueue" } }

This approach works for any property that accepts a basic type. But what about bean references? In this case, you can't use the bean property overrides. Fortunately, the most common bean reference you are likely to want to override, the message converter, has a dedicated configuration option:

rabbitmq.messageConverterBean = "myCustomMessageConverter"

This is a global setting that accepts the name of a message converter bean. For the rare occasions that you need to override other bean references, you can declare your own <serviceName>_MessageListenerContainer or <serviceName>_RabbitAdapter beans in resources.groovy.

Finally, you can override some of the global config options on a per-service basis:

rabbitmq {
    services {
        messageStoreService {
            concurrentConsumers = 50
            disableListening = true
        }
    }
}

There are many options for customisation and we hope the above will get you started.