Grails, Heroku, MongoDB and PostgreSQL

Getting it all working together

Chris Wintle
Groovy & Grails

--

When starting development on a new greenfield project, I decided that i’d try using a cloud hosted solution, along with my old pal Grails.

I’d heard good things about Heroku from other developers, so thought i’d give it a shot.

One complication I had is that we require two very different sets of data — some of it would be very static, same-shape, low-read-frequency data, the other half would be quite the opposite. With that in mind, I thought i’d play with having my Grails app using Postgres and MongoDB together for different domain models.

First, let’s set up our app to run on Heroku!

Create a Heroku app:

At the risk of this post dragging on forever (and the guys at Heroku have summed up the signup/setup process far better than I could), i’ll just point you to the Heroku setup documentation page:

Quick Start — Heroku

You may also want to scan over the Heroku guide to setting up a Grails app. It covers the basics of getting a Grails app running on Heroku:Getting started with grailsSo, Let’s assume we have an empty Heroku application set up and you have the following things available:

  1. A Grails App
  2. A heroku application with mongoHQ/mongoLab and postgres addons installed
  3. The Heroku toolbelt installed on your development machine
  4. Gi

So, we created our Grails app (nothing special here, so I won’t run through any of it).

There are three important Grails plugins required to operate smoothly with Heroku:

http://grails.org/plugin/heroku

http://grails.org/plugin/cloud-support

http://grails.org/plugin/database-session

Nice and simple to install:

(from BuildConfig plugin dependencies section)

compile “:database-session:1.2.1"

compile ‘:heroku:1.0.1'

compile ‘:cloud-support:1.0.8'

The database session plugin is required for session persistence to work nicely in Heroku. It will store session key/value data in a session DB table. Running without it will show reaosnably unpredictable results as sessions get lost.

The cloud support plugin will allow Grails to pick up environment variables for database urls, etc, which get set up by Heroku depending on the add-ons that you have installed in your Heroku app.

The Heroku plugin provides new commands to run against your app — personally, I don’t use them, but you may well find them useful so I have included the plugin here.,

Next, let’s set up our Grails app to play nicely with Heroku and MongoDB:

The Grails MongoDB plugin allows GORM functionality to operate on with a MongoDB database (I know, I know, ORM & mongo together isn’t ideal — but in terms of getting up and running quickly, this works like a charm).

Your Domain objects can be persisted, updated and deleted using the standard GORM save() , .delete() methods that GORM tacks on to your classes.

(Installation instructions and documentation can be found here: Mongo plugin docs)

I simply used

compile “:mongodb:1.3.0"

in the BuildConfig.groovy plugin dependencies section.

So next, we actually need to set up our environments to use mongodb as a second datasource.

The config for my production environment looks like so:

production { dataSource { pooled = true dbCreate = “update” } mongo { url = System.env.MONGOHQ_URL pooled = true options { autoConnectRetry = true connectTimeout = 3000 connectionsPerHost = 500 socketTimeout = 60000 threadsAllowedToBlockForConnectionMultiplier = 5 maxAutoConnectRetryTime=5 maxWaitTime=120000 } }

}

Two important things to note:

When we installed the Cloud support plugin, we allowed Grails to use environment variables that are defined by Heroku.

My heroku app contains a Postgres addon, as well as a MongoHQ addon. Heroku sets up a postgres URL that the cloud support plugin picks up and auto-configures the datasource for postgres. Thus the datasource section above is quite sparse — all I need to tell Grails is that I want the datasource to use ‘update’ as its dbCreate parameter.

HOWEVER, at present, the cloud support plugin doesn’t seem to pick up MONGOHQ_URL or MONGOLAB_URL variables automatically, so I had to use the environment variable here in my DataSource config. Not the end of the world — and you may experience different results — but if you can’t get yours working out of the box, try setting the url of your datasource to the appropriate environment variable. You can view all Heroku variables by running:

heroku config

from within your heroku-linked application folder (ie, wherever you would push to Git from)

Make some of our Domain objects map with Mongo

So, like I said at the beginning, I kind of have two totally separate usage-cases for my domain objects. Some of them are just going to sit there and get read out now and again, while others are going to be read constantly and will contain different-sized data quite a lot, thus making noSQL a nice choice for those objects.

Hibernate provides the ability to provide a ‘mapWith’ attribute on a domain object, which tells it which datasource to use for mapping that Object.

Adding:

static mapWith = ‘mongo’

To a domain class will make that domain class will use the datasource called ‘mongo’ for mapping on this class (if you were to exclude this line, GORM/Hibernate would use the default datasource).

NOTE: Hibernate/GORM doesn’t let you persist a domain object with MongoDB, if any of the object’s attributes are null (by default) (at least at the time of writing). No errors get thrown, GORM just returns ‘null’ as the result of the save. So make sure you set all of your object’s attributes before saving, even if it’s to set them as -1, “”, [], whatever.

OR you can set

grails.gorm.default.constraints = { ‘*’(nullable: true) }

in your Config.groovy

Persisting domain objects

With all this in place, you can call .save() on any domain object. Hibernate will deal with the rest and will (at flush) persist your object to the appropriate database. Other than the ‘no null attributes’ gotcha I just mentioned, that’s all there is to it).

Personally, for my own piece of mind, I send to use .save(flush:true), which forces hibernate to flush (persist to db) at this point. Hibernate is supposed to work out when to do this itself and a lot of developers will say that you should let it do it’s thing — you’re welcome to agree with them, I’ve just beaten my head against hibernate strangeness too many times over my career to really trust it!)

Retrieving domain objects

Retrieving domain objects can be accomplished by GORM’s brilliant findByAttribute method:

.findBy[Attribute]([attributeValue])

So if your class has a ‘name’ attribute, you could use:

MyClassName.findByName(“an example name”)

This works really nicely, but you can alternatively use Criteria (I had inconsistent results with using ‘findBy’ with mongo-mapoped domain classes, but your mileage may vary). There’s a really short way of creating a Criteria inline:

MyClassName.withCriteria({

eq(‘name’, ‘an example name’)

})

This will return a set of results for your Criteria (just like if you were using criteria for a relational-db-mapped domain class).

I won’t go into the specifics of how Criteria work here, but the Grails doc entry for the method is here:

withCriteria

If you read that, then look at the createCriteria (createCriteria) method, you should have enough grounding to work on

And that’s it!

That should cover the essentials on getting set up. On the whole, i’ve had a great experience with getting everything set up with Heroku.

If anyone’s trying to get something similar up and running and is running into trouble, let me know and i’ll see if I can be of any help! (similarly, if anyone has had better results with a simpler set up, I am all ears)

Oh, one final thing….

If you’re using the spring security grails plugin to manage SSH-forcing… you’re going to run into a fun little issue with how spring security and Heroku’s load balancers (fail to) work together….

Details and solution here — turned out to be a very simple fix to do with setting various Config.groovy spring security settings regarding secure header names

For the lazy, the chunk of my Config.groovy that fixed the issue is here:

grails.plugins.springsecurity.secureChannel.useHeaderCheckChannelSecurity = true
grails.plugins.springsecurity.portMapper.httpPort = 80
grails.plugins.springsecurity.portMapper.httpsPort = 443
grails.plugins.springsecurity.secureChannel.secureHeaderName = ‘X-FORWARDED-PROTO’
grails.plugins.springsecurity.secureChannel.secureHeaderValue = ‘http’

grails.plugins.springsecurity.secureChannel.insecureHeaderName = ‘X-FORWARDED-PROTO’
grails.plugins.springsecurity.secureChannel.insecureHeaderValue = ‘https’

Kudos to gsAndrew at the Heroku discussion center for helping me with that one.

--

--