Monday, November 22, 2010

Grails Quick Tip : External Configs

A pretty decent bloke once said that "Simplicity is the ultimate sophistication".... and that is what I like about this idea; it is so damn simple and the rewards gained from it are vast. Well maybe not vast.... but there's a few. The idea I am harping on about is, external configuration files in Grails.

Ok so, the sales pitch. You have created the worlds greatest application, (not difficult with Grails) and you are on top of the world! Then suddenly, the emails you have configured to send out every minute to remind you how great you are, have stopped! What the???? The mail configuration settings have changed???? Uh oh, we are going to have to redeploy the application?? I am in the airport! Uploading my 30MB war file is going to take hours... weeks.... DAYS! I haven't tagged the code and the war might contain untested code!!!!

Relax... breathe... there is an alternative!

External configuration files in Grails allow you to extract the tidbits from Config.groovy that you feel are important. Things like the mail configuration that can change at any time. Others like your datasource password which you don't really want checked into git, subversion or whatever version control you may be using!

Enabling external configuration files in your Grails application is as easy as uncommenting a few lines of code in the top of your Config.groovy, the one's that look like this :


// grails.config.locations = [ "classpath:${appName}-config.properties",
// "classpath:${appName}-config.groovy",
// "file:${userHome}/.grails/${appName}-config.properties",
// "file:${userHome}/.grails/${appName}-config.groovy"]


For this example, let's work with this simple case :


grails.config.locations = ["file:${userHome}/.grails/${appName}Config.groovy"]

Here we have defined an external config that will sit in a hidden grails folder in the user's home directory. userHome is a variable added to the binding of Config.groovy at runtime and works on windows and unix based boxes alike. Similarly, appName is also a variable that is added to the binding at runtime containing the name of your Grails app.

This is all well and good but how does it work in the real world??? A very real scenario is that you will want to use external Config files in production and use the regular Config.groovy during development. No one wants to navigate outside their Grails application directory during development, we are not savages after all!

So..... how do we configure grails to look at an external configuration file in production and still have the flexibility of using Config.groovy on development?? Well you can use the environments closure in Config.groovy like so :


environments {
production {
//all my production configs are externalized
grails.config.locations = ["file:${userHome}/.grails/${appName}Config.groovy"]
}
development {
//the dev environment is none the wiser!
grails {
mail {
host = "mail.server.com"
'default' {
from = "mail@me.net"
}
port = 25
username = "mail@me.com"
password = "password"
props = ["mail.smtp.auth": "true",
"mail.smtp.socketFactory.port": "25",
//"mail.smtp.socketFactory.class":"java.net.ssl.SSLSocketFactory",
"mail.smtp.socketFactory.fallback": "false"]
}
}
}
test {
grails.gorm.failOnError = true
}
}


You can also declare settings that apply to all environments outside of this closure. This has the added bonus of reducing the 'noise' in your production config which should be kept as clean and as crisp as possible.

Now thinking back to our imaginary scenario at the beginning, our jet set developer can now just log into the server, change the offending setting and bounce the server! (Another step would be to database the settings to prevent bouncing but this is slightly off topic.)

It is also possible to refactor your dataSource config options to your external file... something along these lines will work :



dataSource {
pooled = true
driverClassName = "com.mysql.jdbc.Driver"
username = "root"
password = "password"
url = "jdbc:mysql://localhost/pretty_db"
}


Huzzah! Now if you need to change db password, you do not need to redeploy a new war!

Honestly, one of the more convenient advantages of doing this is the ability to work with the logging of your application in production. This can be a pain when you first deploy to an app server on a production server away from the cushy dev environment that Grails provides!

It is worth noting too that you should set your external files up with permissions of 600, the owner of the file being the user that you run tomcat or your other container with.

So one simple change has got us :
  • Ability to change Config options without having to build a war
  • Separated our Datasource options from our application... I assume the DBA's will thank you!
  • Tuning and changing logging has now become a lot easier
  • Your war can now be transfered to different servers with different file systems and all you have to do is change a config file...
In these recessionary times, you cannot argue with that value! Thanks to Tim Berglund for showing how easy this was at his talk at SpringOne2Gx this year :)

This is a great post on using environment variables to achieve this :


UPDATE : It looks like someone has created a plugin that allows you to reload the external config without a restart (I have not used this but should be reliable :) ):


Hope this saves someone the hassle of re-warring for silly reason... it is quite the pain!

John :)

5 comments:

  1. Spectacular. Best blog ever.

    ReplyDelete
  2. Great blog, but is their a way to protect the database username and password?

    ReplyDelete
  3. Hi, where do you recommend I should put that external config file location? I think it should be inside the app's folder but how do I do that?

    ReplyDelete
  4. You seem to reccommend using the internal config for development, but doing so does place passwords in the git repository. It is admittedly inconvenient to place development config info in an external file, especially when working with a team, but I see no other alternative. Is there a better way?

    ReplyDelete