The Artima Developer Community
Sponsored Link

Musings on Language and Design
Creating a Domain Specific Language with Groovy
by Jeremy Meyer
May 8, 2010
Summary
An simple example of how easy it is to make a fairly powerful DSL using the dynamic features and powerful sytax of Groovy

Advertisement

DSLs with Groovy

Creating Domain Specific Languages with Groovy

In my last blog I talked about some of the differences between Groovy and Java syntax and argued that anyone making a paradigm shift from Java to dynamic languages and DSLs (domain specific languages) would actually have a fairly easy time of it with Groovy because of its seamless integration with Java. The road to discovering all of Groovy's powerful and "cool" features has the soft grassy verge of Java compatibility. This means that (whilst not ideal) you can experiment and take a bit of a risk with the new language without blowing project deadlines or running into feature deprivation. The worst case scenario is that you can do things in the Java way, until you can refactor to Groovy paradigms.

I also said that Groovy syntax, cool features and operators (like the spread operator) are not necessarily the real reason you should start using Groovy, but the dynamism and the ability to make DSLs is. I want to demonstrate what that dynamism means, and how one might create a tiny DSL (using a Grails-like feature as an example).

To make a DSL useful, we want to end up with a syntax that is functional enough, simple to read and simple to write. Let's look at how Grails does this with a validation DSL in its domain classes. Don't worry if you don't use or know anything about Grails, this is an elegantly simple example and speaks for itself.

class Customer {
   int age
   String name
   static constraints = {
      age(size:18..65)
      name(size:3..20, blank:false)
   }
}
Without thinking about what language it is written in, try and intuit what it might mean. We have a Customer class with two properties, age and name, and some sort of named block, "constraints" with matching age and name listed. Each property name is followed by brackets, inside of which are some descriptions of constraints for those properties. This is very intuitive. It is pretty obvious that the age should be between 18 and 65 and the name should be between 3 and 20 characters and shouldn't be blank. Provided someone gave you a list of allowable constraints (size, blank, zero, date etc.) you would be able to quickly and easily create your own class with defined validation.

But is this Groovy?

The answer is yes and no. It is actually valid Groovy syntax, but as it stands, it is somewhat meaningless and somewhat useless. Right now, syntactically, we have a class definition which has two properties, and a static closure. The closure contains calls to two fictitious methods, age() and name() which both take maps as parameters. What do I mean by fictitious methods? Simply that syntactically we have two method calls, but to methods which don't exist. If we were to run that closure, we would just get the following error:

groovy.lang.MissingMethodException:
No signature of method: Customer$__clinit__closure1.age() is applicable for argument types:
(java.util.LinkedHashMap) values: [[size:18..65]]

By the way, understanding closures is fundamental to using Groovy effectively. There is a lot of information about them available online. If you are unsure about them, a few web searches will put you straight. Otherwise, just think of them as blocks of code which can be passed around in parameters and run when required. I will talk about them a bit more below.

So what good is this phoney code? Well, the syntax looks clean and tidy and has clear affordance, i.e. it shows you intuitively what it should do. Groovy's friendly map syntax (using comma seperated key/value pairs) gives us neat looking parameter lists. This makes it an effective DSL. It is a language-within-a-language which kind of has its own syntax and structure based in the domain of constraining values. That makes programming with constraints very easy; easy to remember, easy to code, easy to read and understand.

But.. it doesn't do anything yet, and since we have a closure with method calls we can't really use, some clever work needs to be done to make this DSL work for us. Let's look at some of the main Groovy mechanisms we need.

Method Interception

One of the dynamic features of Groovy is that it can intercept methods. The simplest way to do this is to override the invokeMethod method on your class. In its simplest usage, every time you call a non-existent method on your object, invokeMethod will be called first, and passed the method name and the parameter list. The example below just prints the name of the method being called.

class Worker {
   Object invokeMethod(String name, Object args) {
      println “${name} method called.”
   }
   ..
} 
Using this simple technique, if a method actually does exist, that method will be called, and the invokeMethod will not. See the GroovyInterceptable interface for details of how to implement more complex behaviour.

Dynamic Invocation

As a corollary to Method Interception, Groovy can call methods and get and set properties dynamically. You can call a method by calling the invokeMethod method directly, and you can set and get properties by using setProperty, as in clown.setProperty(“nose”, “red”), the dynamic equivalent of clown.setNose(“red”). Doing this in a language like Java would require much more code.

Closures and Delegates

Since a closure is a defined block of code that is not actually run at the time it is defined, it needs to be able to keep track of its definition-time state. The Groovy mechanism for this is the delegate. When you define a closure, it keeps a reference to this, its “birthday” context in its delegate property. A method call in a closure, will resolve to the closure's delegate by default. If you want your closure to call a method on an object other than the one which defined it, you have to set the delegate of your closure to point to that object.

Putting them together


Lets look at the code for a simple validator:
class Validator {
def subject
public void validate(def o) {
subject = o
o.constraints.setDelegate(this)
o.constraints.call()
println "Validation complete."
}


Object invokeMethod(String name, Object args) {
def val = subject.getProperty(name)
args[0].each {
switch(val?.class) {
case null: if (it.key =="blank" && !val)
println "failed: property '${name}' is null."
break
case Integer :
if (it.key == "size" && !(it.value.contains(val)))
println "failed: Integer property '${name}' has value '${val}' not in range '${it.value.inspect()}'."
break
case String: if (it.key =="size" && !it.value?.contains(val.length()))
println "failed: String property '${name}' has value '${val}' not in length range '${it.value.inspect()}'."
break
default:
break
}
}

}
}

The validate method references the object's constraints closure, sets the delegate of the closure to point to itself and then runs the closure. This is where the interesting stuff happens. The closure calls the two methods "age" and "name". These are called on the validator object (which is the new delegate of the closure). Since the methods do not exist on the validator object, the invokeMethod handler is called, which is where the validation actually takes place.

In invokeMethod, we can find out from the parameters the name of the method that was attempting to be called and get the value of the property with that name, in the line:

def val = subject.getProperty(name)

This is where the "language" comes into play. We are using bogus method names to point us to properties which need validating. We can use Groovy's default exception behaviour here to handle the case where the method name in the constraint closure doesn't match a property name. If the property is not found, Groovy will throw a groovy.lang.MissingPropertyException. Once we have the property value, we look at the other arguments, the maps, which were passed to our bogus method call. They contain the key/value pairs "size:" and "blank:". We can make good use of Groovy's range syntax here for easy validation:

it.value.contains(val)
In the switch statement we can check the actual property value for its type and so allow for different validation behaviour (i.e. size for a String means something different from size for an Integer). Note that we can use the safe navigation operator, "?." to ensure that if the value is null, we will catch it in the switch statement in the null case, rather than throw a null pointer exception:

switch(val?.class)

That's It!


About 30 lines of code later, we have a (somewhat primitive) validator, but one which allows us to use a Domain Specific Language approach to define our constraints. Groovy's closures, powerful operators and syntax and its easy introspection have made it a fairly simple task.
To use our validator, we create a Customer, create a Validator and call the validate method. Note that you are more likely to want a static validation method on the Validator class if you want to use this for production code, but this demonstrates the point.

c = new Customer(age: 2, name:null)
v = new Validator()
v.validate(c)
If we run this we get:
size validation failed: Integer property 'age' has value '2' not in range '18..65'.
blank validation failed: property 'name' is null.
Validation complete.
Simple!

References

http://groovy.codehaus.org/Writing+Domain-Specific+Languages
http://www.martinfowler.com/bliki/DomainSpecificLanguage.html

Talk Back!

Have an opinion? Readers have already posted 9 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Jeremy Meyer adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Jeremy has been designing and developing software for over 20 years, as well as teaching its mastery. He is fascinated by all aspects of architecture, design and development, the philosophical, the psychological and the aesthetic. He currently heads up the training division at hybris Software, a fast growing and very exciting eCommerce company.

This weblog entry is Copyright © 2010 Jeremy Meyer. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us