Monday, May 30, 2011

Some DSL fun with Groovy 1.8

One of my colleagues at work was recently tasked with creating a query language for the new integrated infrastructure we're building out. I think he is ultimately going to go with NodeJS, but after reading Groovy's recent 1.8.0 release notes, I thought I'd try my hand at doing with Groovy.

I typically like to work towards a good goal, so I started with a few directional ideas for what I wanted the language to look like. First, the new language is going to be internally (and perhaps externally) referred to as CQL (pronounced cee-que-el), not to be mistaken for SQL (ceequel). So I wanted the language itself to differentiate itself. That rules out stuff like, "select foo from bar where foo.id = 'foo1'".

Second, I've long had a preference for "finder" methods over "getter" methods for data-access level classes. This also happens to go along with Grails' dynamic finders. So, I wanted to stay away from a language like "get user where firstname = 'foo' and lastname = 'bar'". Personally, I find the "find by" language much more user friendly anyway.

With these ideas in mind, and not having a ton of experience with the new command chains in Groovy, my rough goal was to create something like this:
find entity by someField: "value" or by someOtherField: "value"

Here is what I actually ended up with:
find user("email", "firstName") by email: "eric@example.com" or email: "eberry@example.com"

Pretty darn close, perhaps even better actually. There's a nice way to specify which fields from User that I really want, and I think it really reads well.

This actually translates to this with parenthesis and dots:
find(user("email", "firstName")).by(email: "eric@example.com").or(email: eberry@example.com")

Here's how I did it.
First let's take care of the find, and user methods:
def find(it) { it }
def user(String[] fields = [] ) {
   new UserFinder(fields: fields as Set)
}
Basically the 'find' method is just for syntax sake, it's really not needed and simply returns whatever object is passed into it. In this case, it's going to be a UserFinder which is created by the 'user' method.

This gives us a nice encapsulation of concerns, the UserFinder is responsible for finding users.
class UserFinder {
   Set fields
   Map byFields
   Map orFields
   UserFinder by(Map byFields) {
      this.byFields = byFields
      return this
   }
   UserFinder or(Map orFields) {
      this.orFields = orFields
      return this
   }
   def find() {
      ... do find work ...
   }
}


That's really it. Note that since both the 'by' and the 'or' methods take maps you could provide multiple fields there, eg:
find user("firstName", "lastName") by email: "eric@example.com", id: 1 or email: "eberry@example.com", id: 2

Some benefits I see with this approach is that it's easy to separate out the concerns. The "Finder" classes are responsible for doing the actual finding. A groovy script template can be created, and be set up so that the 'user' function and classes are imported, which makes it easy to figure out what functionality is available. Obviously some sanitisation of user queries needs to be done, but besides that execution of user supplied queries is quite safe as the only methods that can be called are those that are provided within the script template. Lastly, groovydoc could be used to produce some documentation without a lot of extra work.

Another interesting idea might be to actually provide a client library which would mean the end user could build these queries in code, and it would look exactly the same as it would be sent over.

Cheers.
Eric

Thursday, May 5, 2011

A Gradle Templates plugin

I'm almost done with my Gradle Templates plugin. All the information on how to use it can be found on it's wiki page. All that's really needed now is to finish up the Scala support.

I'm pretty happy with the result. The plugin supports all the built in Gradle plugins: Java, Groovy, War (webapp), and soon Scala. It also has tasks for starting your own standalone Gradle Plugin.

It's my hope that this plugin will be helpful to others getting started with the Gradle build system.

A Quick Start:
To start using the plugin you need to have Gradle, and Bazaar VCS installed.

Then you install the plugin by downloading the source and running the 'installPlugin' task.

bzr branch lp:gradle-templates

Or if you prefer Git, there is a github mirror.

Then run the installPlugin task in the newly checked out project.

gradle installPlugin

Usually, I do all my development work in a directory under my home directory like so:
/home/elberry/development/projects
So to use the 'create*Project' tasks, I've created a build.gradle file under my 'projects' directory, which simply contains the following:
apply plugin: "templates"
This provides me with the following "Template" tasks (running 'gradle tasks').
Template tasks
--------------
createGradlePlugin - Creates a new Gradle Plugin project in a new directory named after your project.
createGroovyClass - Creates a new Groovy class in the current project.
createGroovyProject - Creates a new Gradle Groovy project in a new directory named after your project.
createJavaClass - Creates a new Java class in the current project.
createJavaProject - Creates a new Gradle Java project in a new directory named after your project.
createScalaClass - Creates a new Scala class in the current project.
createScalaProject - Creates a new Gradle Scala project in a new directory named after your project.
createWebappProject - Creates a new Gradle Webapp project in a new directory named after your project.
initGradlePlugin - Initializes a new Gradle Plugin project in the current directory.
initGroovyProject - Initializes a new Gradle Groovy project in the current directory.
initJavaProject - Initializes a new Gradle Java project in the current directory.
initScalaProject - Initializes a new Gradle Scala project in the current directory.
initWebappProject - Initializes a new Gradle Webapp project in the current directory.

As explained in the wiki, the 'create*Project' tasks are intended for creating new projects, and new directories. The 'init*Project' tasks are intended to be used within a previously created directory where you want to set this directory up as a new Gradle project.

So, to get started with your own webapp, you can now run 'gradle createWebappProject'.

Answer a few basic questions:
[503] gradle createWebappProject
> Building > :createWebappProject
??> Project Name: TestWebappProject

??> Use Jetty Plugin? (Y|n)[n] y
:createWebappProject

BUILD SUCCESSFUL

Total time: 13.207 secs

And you're done. You can 'cd' into your new project and run 'gradle jettyRun' and have your webapp up and running on port 8080 in no time.

Cheers, and happy coding. :)
Eric