Practical Groovy Domain-Specific Languages - Guillaume Laforge - Usi 2009
1. Practical Domain-Specific
Languages with Groovy
Guillaume Laforge
Groovy Project Manager
SpringSource
glaforge@gmail.com
jeudi 2 juillet 2009
2. Guillaume Laforge
• Groovy Project Manager
• JSR-241 Spec Lead
• Head of Groovy Development
at SpringSource
• Initiator of the Grails framework
• Co-author of Groovy in Action
• Speaker: JavaOne, QCon, JavaZone, Sun TechDays,
Devoxx, The Spring Experience, JAX, Dynamic Language
World, IJTC, GR8Conf, DSL DevCon and more...
2
jeudi 2 juillet 2009
3. A few words about Groovy
• Groovy is a dynamic language for the JVM
– with a Meta Object Protocol
– compiles directly to bytecode, seamless Java interop
• Open Source ASL 2 project hosted at Codehaus
• Relaxed grammar derived from Java 5
– + borrowed good ideas from Ruby, Python, Smalltalk
• Fast... for a dynlang on the JVM
• Closures, properties, optional typing, BigDecimal by
default, nice wrapper APIs, and more...
3
jeudi 2 juillet 2009
4. nda
Ag e
• The context and
the usual issues we face
• Some real-life examples of Domain-
Specific Languages
• Groovy’s DSL capabilities
• Integrating a DSL
in your application
• Considerations to remember when
designing your own DSL
4
jeudi 2 juillet 2009
7. Developer producing
LOLCODE
HAI
CAN HAS STDIO?
I HAS A VAR
IM IN YR LOOP
UP VAR!!1
VISIBLE VAR
IZ VAR BIGGER THAN 10?
KTHXBYE
IM OUTTA YR LOOP
KTHXBYE
jeudi 2 juillet 2009
11. DSL: a potential solution?
• Use a more expressive language than a general purpose
one
• Share a common metaphore of understanding between
developers and subject matter experts
• Have domain experts help with the design of the business
logic of an application
• Avoid cluttering business code with too much boilerplate
technical code
• Cleanly separate business logic from application code
• Let business rules have their own lifecycle
11
jeudi 2 juillet 2009
18. nda
Ag e
• The context and
the usual issues we face
• Some real-life examples of Domain-
Specific Languages
• Groovy’s DSL capabilities
• Integrating a DSL
in your application
• Considerations to remember when
designing your own DSL
14
jeudi 2 juillet 2009
19. A collection of DSLs
• In our everyday life, we’re surrounded by DSLs
– Technical dialects
– Notations
– Business languages
15
jeudi 2 juillet 2009
28. Real-life Groovy examples
• Anti-malaria drug resistance simulation
• Human Resources employee skills representation
• Insurance policies risk calculation engine
• Loan acceptance rules engine for a financial platform
• Mathematica-like lingua for nuclear safety simulations
• Market data feeds evolution scenarios
• and more...
24
jeudi 2 juillet 2009
29. nda
Ag e
• The context and
the usual issues we face
• Some real-life examples of Domain-
Specific Languages
• Groovy’s DSL capabilities
• Integrating a DSL
in your application
• Considerations to remember when
designing your own DSL
25
jeudi 2 juillet 2009
30. A flexible & malleable syntax
• No need to write full-blown classes, use scripts
• Optional typing (def)
– in scripts, you can even omit the def keyword
• Native syntax constructs
• Parentheses & semi-colons are optional
• Named arguments
• BigDecimal by default for decimal numbers
• Closures for custom control structures
• Operator overloading
26
jeudi 2 juillet 2009
31. Scripts vs classes
• Hide all the boilerplate technical code
– an end-user doesn’t need to know about classes
– public class Rule {
public static void main(String[] args) {
System.out.println(“Hello”);
}
}
– println “Hello”
27
jeudi 2 juillet 2009
32. Optional typing
• No need to bother with types or even generics
– unless you want to!
• Imagine an interest rate lookup table method returning
some generified type:
– Rate<LoanType, Duration, BigDecimal>[]
lookupTable() { ... }
def table = lookupTable()
• No need to repeat the horrible generics type info!
28
jeudi 2 juillet 2009
33. Native syntax constructs
• Lists
– [Monday, Tuesday, Wednesday]
• Maps
– [CA: ‘California’, TX: ‘Texas’]
• Ranges
– def bizDays = Monday..Friday
– def allowedAge = 18..65
– You can create your own custom ranges
29
jeudi 2 juillet 2009
34. Optional parens & semis
• Make statements and expressions
look more like natural languages
– move(left);
– move left
30
jeudi 2 juillet 2009
35. Named arguments
• In Groovy you can mix named and unnamed arguments
for method parameters
– named params are actually put in a map parameter
– plus optional parens & semis
• take 1.pill,
of: Chloroquinine,
after: 6.hours
• Corresponds to a method signature like:
–def take(Map m, MedicineQuantity mq)
31
jeudi 2 juillet 2009
36. BigDecimal by default
• Main reason why financial institutions often decide to use
Groovy for their business rules!
– Although these days rounding issues are overrated!
• Java vs Groovy for a simple interpolation equation
• BigDecimal uMinusv = c.subtract(a);
BigDecimal vMinusl = b.subtract(c);
BigDecimal uMinusl = a.subtract(b);
return e.multiply(uMinusv)
.add(d.multiply(vMinusl))
.divide(uMinusl, 10, BigDecimal.ROUND_HALF_UP);
• (d * (b - c) + e * (c - a)) / (a - b)
32
jeudi 2 juillet 2009
37. Custom control structures
Thanks to closures
• When closures are last, they can be put “out” of the
parentheses surrounding parameters
• unless (account.balance < 100.euros,
{ account.debit 100.euros })
• unless (account.balance < 100.euros) {
account.debit 100.euros
}
• Signature def unless(boolean b, Closure c)
33
jeudi 2 juillet 2009
38. Operator overloading
a + b a.plus(b)
a - b a.minus(b)
• Currency amounts
a * b a.multiply(b) – 15.euros + 10.dollars
a / b a.divide(b)
a % b a.modulo(b)
• Distance handling
a ** b a.power(b) – 10.kilometers - 10.meters
a | b a.or(b)
a & b a.and(b) • Workflow, concurrency
a ^ b a.xor(b) – taskA | taskB & taskC
a[b] a.getAt(b)
a << b a.leftShift(b) • Credit an account
a >> b a.rightShift(b) – account << 10.dollars
+a a.positive() account += 10.dollars
-a a.negative() account.credit 10.dollars
~a a.bitwiseNegate()
34
jeudi 2 juillet 2009
40. Groovy’s MOP
• All the accesses to methods, properties, constructors,
operators, etc. can be intercepted thanks to the MOP
• While Java’s behavior is hard-wired at compile-time in
the class
• Groovy’s runtime behavior is adaptable at runtime
through the metaclass.
• Different hooks for changing the runtime behavior
– GroovyObject, custom MetaClass implementation, categories,
ExpandoMetaClass
36
jeudi 2 juillet 2009
41. Adding properties to numbers
• Three possible approaches
– create a Category
• a category is a kind of decorator for default MCs
– create a custom MetaClass
• a full-blown MC class to implement and to set on the POGO instance
– use ExpandoMetaClass
• friendlier DSL approach but with a catch
37
jeudi 2 juillet 2009
42. Adding properties to numbers
with an ExpandoMetaClass
• Number.metaClass.getMeters = {->
new Distance(delegate, Unit.METERS)
}
100.meters
38
jeudi 2 juillet 2009
44. The Groovy MarkupBuilder
• def mkp = new MarkupBuilder()
mkp.html {
head {
title “Groovy in Action”
}
body {
div(width: ‘100’) {
p(class: ‘para) {
span “Best book ever!”
}
}
}
}
40
jeudi 2 juillet 2009
45. A builder for HR
• softskills {
ideas {
capture 2
formulate 3
}
...
}
knowhow {
languages {
java 4
groovy 5
}
...
}
41
jeudi 2 juillet 2009
46. A builder for HR
• softskills {
ideas {
capture 2
formulate 3
}
...
}
knowhow {
languages {
java 4
groovy 5
}
...
}
41
jeudi 2 juillet 2009
47. Builders
• Builders are...
– a mechanism for creating any tree-structered graph
– the realization of the GoF builder pattern at the syntax level in
Groovy
– simply a clever use of chained method invocation, closures,
parentheses omission, and use of the GroovyObject methods
• Existing builders
– XML, Object graph, Swing, Ant, JMX, and more...
42
jeudi 2 juillet 2009
49. AST Transformations
• Two kinds of transformations
– Global transformations
• applicable to all compilation units
– Local transformations
• applicable to marked program elements
• using specific marker annotations
44
jeudi 2 juillet 2009
50. Example #1: @Singleton
• Let’s revisit this evil (anti-)pattern
! public class Evil {
public static final Evil instance = new Evil ();
private Evil () {}
Evil getInstance() { return instance; }
}
• In Groovy
! @Singleton class Evil {}
• Also a “lazy” version
! @Singleton(lazy = true) class Evil {}
45
jeudi 2 juillet 2009
51. Example #2: @Delegate
Not just for managers!
• You can delegate to fields of your classes
– class Employee {
def doTheWork() { “done” }
}
class Manager {
@Delegate
Employee slave = new Employee()
}
def god = new Manager()
assert god.doTheWork() == “done”
• Damn manager who will get all the praise...
46
jeudi 2 juillet 2009
52. Global transformations
• Implement ASTTransformation
• Annotate the transfo specifying a compilation phase
• @GroovyASTTransformation(phase=CompilePhase.CONVERSION)
public class MyTransformation
implements ASTTransformation {
public void visit(ASTNode[] nodes, SourceUnit unit)
{ ... }
}
• For discovery, create the file META-INF/services/
org.codehaus.groovy.transform.ASTTransformation
• Add the fully qualified name of the class in that file
47
jeudi 2 juillet 2009
53. Local transformations
• Same approach as Global transformations
• But you don’t need the META-INF file
• Instead create an annotation to specify on which element
the transformation should apply
• @Retention(RetentionPolicy.SOURCE)
@Target([ElementType.METHOD])
@GroovyASTTransformationClass(
["fqn.MyTransformation"])
public @interface WithLogging {...}
48
jeudi 2 juillet 2009
54. nda
Ag e
• The context and
the usual issues we face
• Some real-life examples of Domain-
Specific Languages
• Groovy’s DSL capabilities
• Integrating a DSL
in your application
• Considerations to remember when
designing your own DSL
49
jeudi 2 juillet 2009
55. Various integration mechanisms
• Java 6’s javax.script.* APIs (aka JSR-223)
• Spring’s language namespace
• Groovy’s own mechanisms
• But a key idea is to externalize those DSL programs
– DSL programs can have their own lifecycle
– no need to redeploy an application because of a rule change
– business people won’t see the technical code
50
jeudi 2 juillet 2009
56. Java 6’s javax.script.* API
• Groovy 1.6 provides its own implementation of the
javax.script.* API
• ScriptEngineManager mgr =
new ScriptEngineManager();
ScriptEngine engine =
mgr.getEngineByName(“Groovy”);
String result = (String)engine.eval(“2+3”);
51
jeudi 2 juillet 2009
57. Spring’s lang namespace
• POGOs (Plain Old Groovy Objects) can be pre-compiled
as any POJO and used interchangeably with POJOs in a
Spring application
• But Groovy scripts & classes can be loaded at runtime
through the <lang:groovy/> namespace and tag
• Reloadable on change
• Customizable through a custom MetaClass
• <lang:groovy id="events"
script-source="classpath:dsl/
eventsChart.groovy"
customizer-ref="eventsMetaClass" />
52
jeudi 2 juillet 2009
58. Groovy’s own mechanisms
• Eval
– for evaluating simple expressions
• GroovyShell
– for more complex scripts and DSLs
• GroovyClassLoader
– the most powerful mechanism
53
jeudi 2 juillet 2009
60. GroovyShell
• A Binding provides a context of execution
– can implement lazy evaluation if needed
• A base script class can be specified
• def binding = new Binding()
binding.mass = 22.3
binding.velocity = 10.6
def shell = new GroovyShell(binding)
shell.evaluate(“mass * velocity ** 2 / 2”)
55
jeudi 2 juillet 2009
61. GroovyClassLoader
• Most powerful mechanism
– could also visit or change the AST
– scripts & classes can be loaded from elsewhere
– more control on compilation
• GroovyClassLoader gcl =
new GroovyClassLoader();
Class clazz = gcl.parseClass(
new File(“f.groovy”));
GroovyObject instance =
(GroovyObject)clazz.newInstance();
instance.setMetaClass(customMC);
56
jeudi 2 juillet 2009
62. Externalize business rules
• Although Groovy DSLs can be embedded in normal
Groovy classes, you should externalize them
• Store them elsewhere
– in a database, an XML file, etc.
• Benefits
– Business rules are not entangled
in technical application code
– Business rules can have their own lifecycle, without requiring
application redeployments
57
jeudi 2 juillet 2009
63. nda
Ag e
• The context and
the usual issues we face
• Some real-life examples of Domain-
Specific Languages
• Groovy’s DSL capabilities
• Integrating a DSL
in your application
• Considerations to remember when
designing your own DSL
58
jeudi 2 juillet 2009
64. Start small, with key concepts
Beware overengineering!
jeudi 2 juillet 2009
71. Various levels of sandboxing
• Groovy supports the usual Java Security Managers
• Use metaprogramming tricks to prevent calling /
instantiating certain classes
• Create a special GroovyClassLoader AST code visitor to
filter only the nodes of the AST you want to keep
– ArithmeticShell in Groovy’s samples
66
jeudi 2 juillet 2009
72. Test, test, test!
• Don’t just test for nominal cases
– Explicitly test for errors!
• Ensure end-users get meaningful error messages
67
jeudi 2 juillet 2009
73. nda
Ag e
• Summary
• Questions & Answers
68
jeudi 2 juillet 2009
74. Summary
• Groovy’s a great fit for Domain-Specific Languages
– Malleable & flexible syntax
– Full object-orientation
• Metaprogramming capabilities
– Runtime metaprogramming
– Compile-time metaprogramming
• Groovy’s very often used for mission-critical DSLs
69
jeudi 2 juillet 2009
75. jeudi 2 juillet 2009
?
I kan haz my cheezburgr naw?
Or do ya reely haz keshtionz?