Tips And Resources For Creating DSLs in Groovy
Paul King had a very good presentation (last year's slides) at JavaZone about why to use Domain-Specific Languages and how to create internal DSLs in Groovy. I'd like to list here few tips that he has mentioned but before we get to that, why would you want to create a DSL? Martin Fowler answers that in his Domain-Specific Languages book (2010). Some of the reasons are to have a higher-level, more focused and conscise representation that also domain experts can read and perhaps even write. You have certainly already used a DSL such as regular expressions, CSS, SQL, Spock's BDD tests, build instructions in Gradle - these are rather technical but sometimes DSLs are also created to be used by business users, f.ex. for anti-malaria drug resistance simulation. (Want more DSLs in Groovy?).
Paul mentions one important thing - you can always make your DSL better, i.e. more fail-proof (case insensitive, support plural endings, ...) and secure and more like a natural language but it all comes at a cost and you must evaluate when the cost overweights the benefit (beware the 80:20 rule).
Some of the Groovy DSL implementation tips:Use a GroovyShell and a custom Binding subclass overriding getVariable(String symbol) so as to return/create an object for unknown "variables" (ex.: "newOrder" -> new Order(), creation of vars for symbols like "h", "km" etc. => 24.km/h) Operator overloading: methods of specific names are invoked for operators (/ -> div, * -> multiply etc.) Use a closure setting its delegate and configuring it the cosure's resolve strategy to delegate unknown methods further to the delegate Create a custom Closure subclass and use that for a closure instead of the default one Use Groovy's Categories (st. like a temporary mixin, activated via the use(category) { .. } form) to add localy methods to classes Use Groovy Builders for tree-like DSLs (builder <-> method calls that take a Closure as argument) Use map attributes to get st. resembling named atributes Use GroovyShell with a custom classloader and compilation phase something to prevent calls of other static and instance methods than allowed
Paul mentions one important thing - you can always make your DSL better, i.e. more fail-proof (case insensitive, support plural endings, ...) and secure and more like a natural language but it all comes at a cost and you must evaluate when the cost overweights the benefit (beware the 80:20 rule).
Some of the Groovy DSL implementation tips:
- Add methods to any object/class via metaprogramming: <class|instance>.metaclass.<method name or getProperty> = {MyType param -> ... } - you can use 'delegate' to refer to the instance
- notice that getProperty is called for anything that isn't a method as in map['key'] == map.key - useful to implement e.g. 1.kg
- Call ExpandoMetaClass.enableGlobally() to propagate methods added to Number also to Integer etc.
Resources
- Book Groovy for Domain-Specific Languages (a review) - I hope I'll get a chance to read this one
- M. Fowler's Domain-Specific Languages book
- Groovy wiki: Writing Domain-Specific Languages
- DZone: Unit-specific DSL using JScience, using Groovy
- M. Fowler's DSL Patterns Catalog