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.
Does that really make it a DSL? You are using Groovy syntax and capabilities, not designing a new syntax and set of capabilities to do the job. It's not a "language", more like a design pattern in groovy.
Interesting and clever use of the closure delegate!
There's a couple of issues I have with the approach, however. First, the constraints are not in proximity to the target features, making it difficult to match up and read the code in terms of understanding what exactly is constrained. Second, by using dynamic method handling we give up type-safety and desing-time feedback. For instance, my editor can't help me complete a constraint nor can it tell me if I've entered an invalid one.
Maybe a better approach could involve annotations e.g.,
class Customer { @constraint( size:18..65 ) int age @constraint( size:3..20, blank:false ) String name }
> however. First, the constraints are not in proximity to > the target features, making it difficult to match up and > read the code in terms of understanding what exactly is > constrained.
I see your point, but actually, I find annotations ugly and difficult to read. It is much more elegant to have a code block which handles all of your constraints. And they match perfectly by name. I think the creators of Grails probably went with the DSL approach precisely to avoid the somewhat messy interspersal of code with annotation.
> give up type-safety and desing-time feedback.
re. type safety. One of the tenets of dynamic languages is that developers have over-emphasised the importance of type safety. There are many, many debates in the literature about this, so I won't discuss here.
> instance, my editor can't help me complete a constraint > nor can it tell me if I've entered an invalid one.
It shouldn't take more than 10 minutes to write a macro in almost any editor to create a constraint block from your code. Even if you can't yet, that is not a problem in principle, it is just that editors have not caught up yet.
Code editors were very primitive when they first came out, but they have evolved constantly, always a few steps behind the language. Code sense type behaviour has already vastly improved in a lot of dynamic language editors and with the right extensibility APIs, it would be fairly easy for developers delivering DSL approaches to create the editor tools to go with them.
I suppose I don't understand what is "ugly" about:
@constraint( size:18..65 ) int age
I'll agree that sometimes annotations are overused, but in this case it sure seems made-to-order.
I also understand the notion that strict type-safety is an overplayed concept. Sometimes it's too burdensome to stay withing static boundaries to get the job done with respect to time and maintainability, esp. meta programming. So I do, in part, appreciate Groovy's dynamic features. Unfortunately, from my perspective, these features are abused more often than not.
The real benefit Groovy offers as a static language is not so much type-safety from compile-time validation, although that is still very high on the list. Today we have vast computing power at our disposal. Editors can perform a staggering amount of static analysis between keystrokes giving us instant feedback and code completion. Static language parsers can achieve most of the benefits of dynamic languages via type inference. Groovy and emerging tools for it strike a near perfect balance in this regard e.g., IntelliJ IDEA's Groovy support.
But if we use Groovy's dynamic features we escape the watchful eyes of our editor and other tools. We lose code completion and instant feedback; we're programming in the dark. Sometimes it's worth the tradeoff (meta programming), but in a lot of cases it's not.
Back to your example. What exactly have we gained over the annotation approach? It's certainly no more readable and it's about the same amount of code. What have we given up? Of course we lose build-time validation. And we've lost design-time feedback -- the editor can no longer tell me what my options are e.g., size, blank, etc. I now have to execute code to validate it syntactically. We've also lost proximity; it's harder to visually inspect if we've missed a constraint. Also, as with most DSL solutions, we've lost familiarity. By inventing something new we've added to the learning curve.
So it seems in this case the dynamic solution isn't quite worth it. At least not to me.
Well I take it back... the bit about a "near perfect balance."
I'm not a Groovy programmer. In fact I've never used it at all, just read bits about it here and there and assumed it was statically typed. To not be a total blow hole I went and read up a bit more about it and wouldn't you know, it's not statically typed -- at all. It just looks that way. Probably a better description of Groovy's type system would be dynamically typed with optional type annotations.
If only Groovy were truly "optionally statically typed" as I've read in other places...
The interceptor/closure technique is very neat. Cool. The actual application for data validation is, to my mind, more of an excuse to show the interceptor technique that a real revelation.
Yes, it's an interesting way to add constraints, but, IMHO, annotations are clearer, and, frankly, simply checking values in the constructor (or setters if you are using them) and throwing exceptions, combined with good comments/javadocs in the code, is just as good.
In the long run, whichever technique is easier to read and maintain should win out. It does require discipline to write in the javadocs that a value will never be null, must be a positive number, etc... And keeping them in synch with the code is tough. So I'm eager for some improvements there.
Minor snit - I don't like the example's use of "size" to mean both the range of an integer, and the length of a String. And the mathematician in me wants to see '[' and '(' options for the bounds for inclusive/exclusive.
I'm expecting Robert to pipe in here with something about database constraints, cause in many cases they are the underlying driving force. How can they best be reflected in the code and easily understood by programmers?
> I suppose I don't understand what is "ugly" about: >
> @constraint( size:18..65 ) > int age >
Probably nothing, but that was not my point. If you had 20 - 30 such attributes, you would have the word @constraint 20 - 30 times, one in between each attribute. Surely you can't think that looks good?
> I also understand the notion that strict type-safety is an > overplayed concept. As I said, not a debate for here. Type Safety / Static vs. Dynamic typing has been done to death, it has become a religious argument.
> Back to your example. What exactly have we gained over the > annotation approach?
It seems I didn't emphasise in the blog that I did not create this idea, it is the approach that Grails uses when defining entity classes and is commonly accepted as a DSL. I merely reverse engineered the DSL to show how one might have implemented it, to showcase some of the dynamic power of Groovy. I assume the creators of Grails did something similar. BTW, annotations fit the definition of Domain Specific Languages.
> If only Groovy were truly "optionally statically typed" as > I've read in other places...
while not exactly that, on the whole i'd (just personally mind you) rather do dsls in scala. i use groovy in my day job and its particular dynamism makes my development cycle longer and more painful than it should be, imhumbleo.