3 Tutorials - Reference Documentation
Authors: Burt Beckwith
Version: 1.0.1
Table of Contents
3 Tutorials
To make things more clear, try one or both of these tutorials. The first is a basic tutorial that gets you started with a simple application that uses a PostgreSQL database and some domain classes. The advanced tutorial is more extensive and includes examples of using NoSQL and messaging.3.1 Basic Tutorial
Here I assume you've got Grails 1.3.7 or 2.0.0 installed, along with Git and the Heroku command line client (as described in Getting Started with Java on Heroku/Cedar) and that you've authenticated to Heroku using the commandline client.Do not create a pom.xml or Procfile.Create the application
$ grails create-app herokutest $ cd herokutest
Install the Heroku plugin
Register a dependency on the plugin (and also on the cloud-support plugin to be sure it's correctly resolved) ingrails-app/conf/BuildConfig.groovy
in the plugins
section:plugins { compile ':heroku:1.0.1' compile ':cloud-support:1.0.8' }
Database configuration
You don't need to change anything inDataSource.groovy
since the plugin reconfigures the settings when the application starts up. The plugin will set the driverClassName
to 'org.postgresql.Driver' and the dialect
to org.hibernate.dialect.PostgreSQLDialect
, and change the url
, username
, and password
to the values detected from the system properties for your PostgreSQL instance.You'll need the JDBC driver for the PostgreSQL database, so add a dependency for it in BuildConfig.groovy
. Add the mavenCentral()
repository (and optionally mavenLocal()
):repositories { grailsPlugins() grailsHome() grailsCentral() mavenLocal() mavenCentral() }
dependencies { runtime 'postgresql:postgresql:8.4-702.jdbc3' }
$ grails create-domain-class database.Author $ grails create-domain-class database.Book
package databaseclass Author { String name String toString() { name } static hasMany = [books: Book] static mapping = { cache true } }
package databaseclass Book { String title String toString() { title } static belongsTo = [author: Author] static mapping = { cache true } }
$ grails generate-all database.Author $ grails generate-all database.Book
Heroku and Git
Heroku uses Git to deploy your application, so initialize a Git repository:$ git init
$ grails integrate-with --git
.settings stacktrace.log target /web-app/plugins /web-app/WEB-INF/classes
$ git add .
$ git commit -m "initial commit"
$ heroku create --stack cedar
Deploy
Deploying just involves pushing to the remote Git repository at Heroku:$ git push heroku master
$ heroku logs
$ heroku ps
Updates
When you update your code, rungit add
for the new and modified files, and commit. Then push again to the remote repo (git push heroku master
) and your app will be stopped, rebuilt, and restarted.
3.2 Advanced Tutorial
Here I assume you've got Grails 1.3.7 or 2.0.0 installed, along with Git and the Heroku command line client (as described in Getting Started with Java on Heroku/Cedar) and that you've authenticated to Heroku using the commandline client.Do not create a pom.xml or Procfile.Create the application
$ grails create-app herokutest $ cd herokutest
Install the Heroku plugin
Register a dependency on the plugin (and also on the cloud-support plugin to be sure it's correctly resolved) ingrails-app/conf/BuildConfig.groovy
in the plugins
section:plugins { compile ':heroku:1.0.1' compile ':cloud-support:1.0.8' }
3.2.1 Database configuration
You don't need to change anything inDataSource.groovy
since the plugin reconfigures the settings when the application starts up. The plugin will set the driverClassName
to 'org.postgresql.Driver' and the dialect
to org.hibernate.dialect.PostgreSQLDialect
, and change the url
, username
, and password
to the values detected from the system properties for your PostgreSQL instance.You'll need the JDBC driver for the PostgreSQL database, so add a dependency for it in BuildConfig.groovy
. Add the mavenCentral()
repository (and optionally mavenLocal()
):repositories { grailsPlugins() grailsHome() grailsCentral() mavenLocal() mavenCentral() }
dependencies { runtime 'postgresql:postgresql:8.4-702.jdbc3' }
$ grails create-domain-class database.Author $ grails create-domain-class database.Book
package databaseclass Author { String name String toString() { name } static hasMany = [books: Book] static mapping = { cache true } }
package databaseclass Book { String title String toString() { title } static belongsTo = [author: Author] static mapping = { cache true } }
3.2.2 Heroku services
Now we'll add some other supported services. These are all optional, but each has a free version so there's no risk in testing them out.Heroku uses Git to deploy your application, so initialize a Git repository:$ git init
$ heroku create --stack cedar
3.2.3 Memcached
To use Memcached as your Hibernate 2nd-level cache, add a dependency for thememcached
plugin in BuildConfig.groovy
:plugins { … compile ':memcached:1.0.2' }
repositories {
…
mavenRepo 'http://raykrueger.googlecode.com/svn/repository' // for hibernate-memcached
mavenRepo 'http://files.couchbase.com/maven2/'
}
inherits('global') { excludes 'ehcache' }
$ heroku addons:add memcache:5mb
heroku
plugin will configure things for you.To see the new environment variables added for Memcached, run$ heroku config
MEMCACHE_PASSWORD => your_password MEMCACHE_SERVERS => mc123.ec456.northscale.net MEMCACHE_USERNAME => app12345%40heroku.com
3.2.4 MongoDB
If you want to try MongoDB you have two options, "MongoLab" or "MongoHQ". Both are supported, so either run$ heroku addons:add mongolab:starter
$ heroku addons:add mongohq:free
$ heroku config
MONGOLAB_URI => mongodb://username:password@server.mongolab.com:27567/heroku_app12345
MONGOHQ_URL => mongodb://username:password@server.mongohq.com:10030/app12345
mongodb
plugin in BuildConfig.groovy
:plugins { … compile ':mongodb:1.0.0.RC3' }
heroku
plugin will configure things for you.Create a domain class to test Mongo:$ grails create-domain-class mongo.MongoThing
package mongoclass MongoThing { String name Integer age Date dateCreated Date lastUpdated static mapWith = 'mongo' }
3.2.5 Redis
If you want to try Redis run$ heroku addons:add redistogo:nano
$ heroku config
REDISTOGO_URL => redis://redistogo:6652dc88c@pike.redistogo.com:12345/
redis-gorm
plugin in BuildConfig.groovy
:plugins { … compile ':redis-gorm:1.0.0.M8' }
heroku
plugin will configure things for you.Create a domain class to test Redis:$ grails create-domain-class redis.RedisThing
package redisclass RedisThing { String name Integer age Date dateCreated Date lastUpdated static mapWith = 'redis' static mapping = { name index: true age index: true } }
3.2.6 RabbitMQ
If you want to try RabbitMQ ensure that your account has access to the private beta for RabbitMQ and run$ heroku addons:add rabbitmq
$ heroku config
RABBITMQ_URL => amqp://username:password@server.rabbitmq.com:12345/virtualhost
rabbitmq
plugin in BuildConfig.groovy
:plugins { … compile ':rabbitmq:0.3.2' }
heroku
plugin will update those. While we're here, let's also configure a queue to work with ("herokuQueue"). We'll update the production
block in Config.groovy with the Heroku values, and the development
block with local values:environments { production { rabbitmq { connectionfactory { username = 'placeholder' password = 'placeholder' hostname = 'placeholder' consumers = 5 } queues = { herokuQueue() } } } development { rabbitmq { connectionfactory { username = 'guest' password = 'guest' hostname = 'localhost' consumers = 5 } queues = { herokuQueue() } } } }
$ grails create-service rabbit.Message
package rabbitclass MessageService { static transactional = false static rabbitQueue = 'herokuQueue' // not thread-safe, just for demo List<String> mostRecentTextMessages = [] void handleMessage(String message) { try { log.info "Text message received at ${new Date()} $message" mostRecentTextMessages << message while (mostRecentTextMessages.size() > 10) { mostRecentTextMessages.remove 0 } } catch (Throwable t) { // demo only - never catch Throwable! t.printStackTrace() } } }
$ grails create-controller rabbit.Message
package rabbitclass MessageController { def messageService def index = {} def sendMessage = { rabbitSend 'herokuQueue', params.message flash.message = "Message sent: '$params.message'" redirect action: 'viewMessages' } def viewMessages = { [messages: messageService.mostRecentTextMessages] } }
grails-app/views/message/index.gsp
to send messages:<html><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta name="layout" content="main" /> <title>RabbitMQ Messaging</title> </head><body> <div class="nav"> <span class="menuButton"> <a class="home" href="${createLink(uri: '/')}">Home</a> </span> </div> <div class="body"> <h1>Send Message</h1> <g:form action='sendMessage'> <div class="dialog"> <table> <tbody> <tr class="prop"> <td valign="top" class="name"> <label for="message">Message</label> </td> <td valign="top" class="value"> <g:textField name="message"/> </td> </tr> </tbody> </table> </div> <div class="buttons"> <span class="button"><g:submitButton name="Send" /></span> </div> </g:form> </div> </body> </html>
grails-app/views/message/viewMessages.gsp
to view messages:<html><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta name="layout" content="main" /> <title>RabbitMQ Messaging</title> </head><body> <div class="nav"> <span class="menuButton"> <a class="home" href="${createLink(uri: '/')}">Home</a> </span> </div> <div class="body"> <h1>View Messages</h1> <g:if test="${flash.message}"> <div class="message">${flash.message}</div> </g:if> <div class="list"> <ul> <g:each in="${messages}" var="m"> <li>${m.encodeAsHTML()}</li> </g:each> </ul> </div> </div> </body> </html>
3.2.7 Tying it all together
We'll need a UI to view the domain classes, so run$ grails generate-all "*"
$ grails create-controller info
package herokutestclass InfoController { def index = { [env: System.getenv()] } }
BuildConfig.groovy
:plugins { … compile ':console:1.1' }
plugins { … compile ':dbconsole:1.1' }
grails.dbconsole.enabled
attribute in the production section of Config.groovy
:production {
…
grails.dbconsole.enabled = true
…
}
BuildConfig.groovy
for the jquery
plugin so jQuery is available (it's automatically registered in 2.0 apps):plugins { … compile ':jquery:1.7.1' }
The GSP created here exposes a lot of information about your application and services, including passwords, a link to auto-login to a database console, and a web-based Groovy console that can run any arbitrary Groovy code. Be sure to guard access to your application with a security plugin, e.g. spring-security-core or ShiroCreate
grails-app/views/info/index.gsp
:
<html><head> <title>Heroku Grails Test</title> <meta name='layout' content='main' /> <meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> <style type="text/css" media="screen"> #nav { margin-top:20px; margin-left:30px; width:228px; float:left; } .homePagePanel * { margin:0px; } .homePagePanel .panelBody ul { list-style-type:none; margin-bottom:10px; } .homePagePanel .panelBody h1 { text-transform:uppercase; font-size:1.1em; margin-bottom:10px; } .homePagePanel .panelBody { background: url(images/leftnav_midstretch.png) repeat-y top; margin:0px; padding:15px; } .homePagePanel .panelBtm { background: url(images/leftnav_btm.png) no-repeat top; height:20px; margin:0px; } .homePagePanel .panelTop { background: url(images/leftnav_top.png) no-repeat top; height:11px; margin:0px; } h2 { margin-top:15px; margin-bottom:15px; font-size:1.2em; } #pageBody { margin-left:280px; margin-right:20px; } </style> </head><body> <body> <div id='nav'> <div class='homePagePanel'> <div class='panelTop'></div> <div class='panelBody'> <h1>Application Status</h1> <ul> <li>App version: <g:meta name='app.version'/></li> <li>Grails version: <g:meta name='app.grails.version'/></li> <li>Groovy version: ${GroovySystem.version}</li> <li>JVM version: ${System.getProperty('java.version')}</li> <li>Controllers: ${grailsApplication.controllerClasses.size()}</li> <li>Domains: ${grailsApplication.domainClasses.size()}</li> <li>Services: ${grailsApplication.serviceClasses.size()}</li> <li>Tag Libraries: ${grailsApplication.tagLibClasses.size()}</li> </ul> <h1>Installed Plugins</h1> <ul> <g:each var='plugin' in='${applicationContext.pluginManager.allPlugins}'> <li>${plugin.name} - ${plugin.version}</li> </g:each> </ul> </div> <div class='panelBtm'></div> </div> </div> <div id='pageBody'> <table> <thead> <tr><th>Name</th><th>Value</th></tr> </thead> <tbody> <tr> <td>DATABASE_URL</td> <td>${env.DATABASE_URL}</td> </tr> <tr> <td>RABBITMQ_URL</td> <td>${env.RABBITMQ_URL}</td> </tr> <tr> <td>REDISTOGO_URL</td> <td>${env.REDISTOGO_URL}</td> </tr> <tr> <td>MONGOHQ_URL</td> <td>${env.MONGOHQ_URL}</td> </tr> <tr> <td>MONGOLAB_URI</td> <td>${env.MONGOLAB_URI}</td> </tr> <tr> <td>MEMCACHE_SERVERS</td> <td>${env.MEMCACHE_SERVERS}</td> </tr> <tr> <td>MEMCACHE_USERNAME</td> <td>${env.MEMCACHE_USERNAME}</td> </tr> <tr> <td>MEMCACHE_PASSWORD</td> <td>${env.MEMCACHE_PASSWORD}</td> </tr> </tbody> </table><g:javascript library="jquery" plugin="jquery" /> <div id='controllerList' class='dialog'> <h2>Links:</h2> <ul> <li>Hibernate: <ul> <li class='controller'> <g:link controller='author'>Author Controller</g:link> </li> <li class='controller'> <g:link controller='book'>Book Controller</g:link> </li> </ul> </li> <li>Redis: <ul> <li class='controller'> <g:link controller='redisThing'>Redis Domain Class</g:link> </li> </ul> </li> <li>Mongo: <ul> <li class='controller'> <g:link controller='mongoThing'>Mongo Domain Class</g:link> </li> </ul> </li> <li>Rabbit: <ul> <li class='controller'> <g:link controller='message'>Send a message</g:link> </li> <li class='controller'> <g:link controller='message' action='viewMessages'>View messages</g:link> </li> </ul> </li> <li>Admin: <ul> <li class='controller'> <g:link controller='console'>Console</g:link> </li> <li class='controller'> <g:link controller='dbconsole'>Database Console</g:link> </li> <li class='controller'> <h:dbconsoleLink>Database Console (autologin)</h:dbconsoleLink> </li> </ul> </li> </ul> </div> </div> </body> </html>
Logging
You should probably turn on debug logging in Config.groovy for the various plugins, e.g.log4j = { error 'org.codehaus.groovy.grails', 'org.springframework', 'org.hibernate', 'net.sf.ehcache.hibernate' debug 'grails.plugin.heroku', 'grails.plugin.memcached', 'grails.plugin.cloudsupport' }
BootStrap
If you don't want to use the auto-login dbconsole link but still have the database console available, you can add some code toBootStrap.groovy
to display the connect information. You can also print out environment variables and system properties while you're there:import grails.plugin.heroku.PostgresqlServiceInfoclass BootStrap { def init = { servletContext -> println "nSystem.getenv():" System.getenv().each { name, value -> println "System.getenv($name): $value" } println "n" println "nSystem.getProperties():" System.getProperties().each { name, value -> println "System.getProperty($name): $value" } println "n" String DATABASE_URL = System.getenv('DATABASE_URL') if (DATABASE_URL) { try { PostgresqlServiceInfo info = new PostgresqlServiceInfo() println "nPostgreSQL service ($DATABASE_URL): url='$info.url', " + "user='$info.username', password='$info.password'n" } catch (e) { println "Error occurred parsing DATABASE_URL: $e.message" } } } }
$ heroku logs
Git
Heroku uses Git to deploy your application, so you'll need a .gitignore file. In 2.0 you can run$ grails integrate-with --git
.settings stacktrace.log target /web-app/plugins /web-app/WEB-INF/classes
$ git add .
$ git commit -m "initial commit"
Deploy
Deploying just involves pushing to the remote Git repository at Heroku:$ git push heroku master
$ heroku logs
$ heroku ps
Updates
When you update your code, rungit add
for the new and modified files and commit. Then push again to the remote repo (git push heroku master
) and your app will be stopped, rebuilt, and restarted.