One of the products we are working on here at Rally lends itself to being developed as a set of services packaged as separate web applications using the Grails framework. Naturally, there is a set of behaviors that all of these apps have in common and need to implement in a consistent way without introducing repetition and cloned code.

For example, we want each application to collect and publish a set of performance metrics using Coda Hale’s Yammer Metrics package.  This package is available as a set of jars that provide some nice Java classes for collecting timings, counters, and other metrics as well as a servlet for making them available in JSON form for a downstream monitoring and collection system.  Normally, each application would have to:

  1. Download and include each jar (there are several) or add maven dependencies.
  2. Modify it’s web.xml file to wire up the servlet
  3. Package up custom extensions and share these via some mechanism (like another jar)

Grails plugins make this really easy.  Let’s walk through the steps needed to package yammer metrics this way.

Step 1: Create an empty plugin

To create a new plugin, run

grails create-plugin yammer-metrics

This creates directory structure for the plugin that closely mimics the application structure used by grails including:

./lib
./src
./src/groovy
./src/java
./YammerMetricsGrailsPlugin.groovy
./grails-app
./grails-app/conf
./grails-app/conf/BuildConfig.groovy

Now we have the outline of a plugin that we can use to accomplish the three steps above to package the yammer metrics for inclusion in any grails application.

Step Two: Get the Jars

To get the yammer metrics jars, we can either add a dependency into grails-app/conf/BuildConfig.groovy stating that we need the jars (currently we are only using metrics-core and metrics-servlet) and to get them from Coda Hale’s repository:

repositories {
     ...
     mavenRepo "http://repo.codahale.com"
}
dependencies {
	runtime 'com.yammer.metrics:metrics-core:2.0.0-BETA18-SNAPSHOT'
	runtime 'com.yammer.metrics:metrics-servlet:2.0.0-BETA18-SNAPSHOT'
}

The BuildConfig.groovy file is a DSL that pretty closely follows the syntax for maven or ivy. We can also simply download the jars manually and place them in the plugin’s lib directory.  Any jars added there will automatically be included in any app using the plugin.  Placing them directly in the lib directory avoids having to have access to repo.codahale.com at build time and ensures that we can always get the required versions of the jars, so we went that route.

Step 3: Wire up the metrics servlet

While the yammer metrics package includes a servlet to expose the metrics, using jars does not give it a way to modify the web.xml file of an including application to wire it up.  Grails provides several hooks that a plugin can use to influence the behavior of an including application.  These are found in the plugin’s descriptor (YammerMetricsGrailsPlugin.groovy in our case) and one of them provides exactly what we need:

def doWithWebDescriptor = { xml ->

	def count = xml.'servlet'.size()
	if(count > 0) {

		def servletElement = xml.'servlet'[count - 1]

		servletElement + {
			'servlet' {
				'servlet-name'("YammerMetrics")
				'servlet-class'("com.yammer.metrics.reporting.MetricsServlet")
			}
		}
		println "***\nYammerMetrics servlet injected into web.xml"
	}

	count = xml.'servlet-mapping'.size()
	if(count > 0) {
		def servletMappingElement = xml.'servlet-mapping'[count - 1]
		servletMappingElement + {

			'servlet-mapping' {
			'servlet-name'("YammerMetrics")
			'url-pattern'("/metrics/*")
		}
	}
	println "YammerMetrics servlet filter-mapping (for /metrics/*) injected into web.xml\n***"
}
 

Now, when any application using this plugin builds its web descriptor (usually via ‘grails run-app’ or ‘grails war’), this code wires in the metrics servlet and maps it to /metrics.

Step 4: Add some home grown extensions

The yammer metrics package rocks, but lacks groovy specific bindings.  To use the TimerMetric class to time a section of groovy code we need to use a block like:

TimerMetric documentTimer = Metrics.newTimer( SnapshotService.class, "document.total.creation.time",
        TimeUnit.MILLISECONDS, TimeUnit.SECONDS )
TimerContext tc = documentTimer.time()
try {
    doSomethingThatNeedsTiming()
} finally {
    tc.stop()
}

But it would be nice to be able to time a closure without all the boiler plate. Something more like:

documentTimer.time() {
    doSomethingThatNeedsTiming()
}

We can easily add classes to our plugin that become available to the application by placing them in the src/groovy directory, so we can add something like the following to make a groovier TimerMetrics class.

package com.yammer.metrics.groovy
import com.yammer.metrics.Metrics

class TimerMetric {

	@Delegate private com.yammer.metrics.core.TimerMetric timerMetric
	String owner
	String name

	TimerMetric( Class<?> owner, String name ) {
		this.owner = owner.name
		this.name = name

		timerMetric = Metrics.newTimer( owner, name,
			TimeUnit.MILLISECONDS, TimeUnit.SECONDS )
	}

	def time( Closure closure ) {
		TimerContext tc = timerMetric.time()
		try {
			closure.call()
		} finally {
			tc.stop()
		}
	}
}

The TimerMetric keeps track of the maximum time consumed in any call to the block. That’s very helpful, but often, we’d like to know a little more about what was happening when a new maximum is hit. We can easily add the capability to do some logging when this happens if we modify our Groovy TimerMetric class to:

package com.yammer.metrics.groovy

import com.yammer.metrics.Metrics
import com.yammer.metrics.core.TimerContext
import java.util.concurrent.TimeUnit
import org.apache.commons.logging.LogFactory

class TimerMetric {

	@Delegate private com.yammer.metrics.core.TimerMetric timerMetric
	String owner
	String name

	private static final log = LogFactory.getLog( com.yammer.metrics.groovy.TimerMetric )
	private ownerLog

	TimerMetric( Class<?> owner, String name ) {
		this.owner = owner.name
		this.name = name

		ownerLog = LogFactory.getLog( owner )
		timerMetric = Metrics.newTimer( owner, name,
			TimeUnit.MILLISECONDS, TimeUnit.SECONDS )
	}

	// Create an empty closure to call whenever a new maximum is achieved.
	// The creator of the TimerMetric can replace this with its own closure.
	def onNewMax = {
	}

	def time( Closure closure ) {
		def oldMax = timerMetric.max()
		TimerContext tc = timerMetric.time()
		try {
			closure.call()
		} finally {
			tc.stop()
		}

		// If this is a new max, call the onNewMax closure
		if( timerMetric.max() > oldMax ) {
			onNewMax.call()
		}
	}

	// Quite often, all we really want to do is log some message when a new
	// max is hit.  Provide a version of the time() method that does this
	// on behalf of the owning class, rather than force it to replace the onNewMax closure

	def time( String maxMessage, Closure closure ) {
		def oldMax = timerMetric.max()
		this.time( closure )
		if( timerMetric.max() > oldMax ) {
			ownerLog.info( "${name} -- New maximum of ${max()} ${durationUnit()} set")
			ownerLog.info( maxMessage )
		}
	}

}

Using the plugin

Once the plugin has been built and archived into a plugin repository (either an internal one or contributed to the Grails central repo), installing it into an application is just a matter of adding a dependency to BuildConfig.groovy or using the ‘grails install-plugin’ command. Doing that and nothing else in this case will add an endpoint to the app at /metrics/metrics that will render some JVM metrics in JSON form.

But wait, there’s more

Packaging third party jars is one of the simplest use cases for a Grails plugin.  They are quite powerful and can do many more advanced things like:

  • Add groovy scripts to the app itself (e.g., a script to create a template for database migrations)
  • Add a build time tool and link it into the build process
  • Add almost any kind of artefact to an app including persistable domain objects, controllers, views, javascript libraries, and css.
  • Create an entirely new type of grails artefact (e.g., the Quartz plugin creates the concept of a schedulable job)
  • Modify an application’s spring configuration
  • Find all of the classes of a particular artefact type (e.g., domain objects) and graft on some dynamic methods (like adding tagging or recycle bin behavior to all domain objects)
  • Modify the application context

Using these features allows a plugin to package up almost any kind of behavior for insertion into multiple applications, taking code reuse to a whole new level.