Groovy For Domain-Specific Languages - Second Edition - Sample Chapter
Groovy For Domain-Specific Languages - Second Edition - Sample Chapter
Second Edition
$ 49.99 US
31.99 UK
"Community
Experience
Distilled"
Fergal Dearle
Second Edition
Sa
pl
e
C o m m u n i t y
E x p e r i e n c e
D i s t i l l e d
ee
Fergal Dearle
Preface
The Java virtual machine runs on everything from the largest mainframe to the
smallest microchip and supports every conceivable application. But Java is a
complex, and sometimes arcane, language to develop with. Groovy allows us to
build targeted single-purpose mini languages, which can run directly on the JVM
along with the regular Java code.
This book provides a comprehensive tutorial on designing and developing mini
Groovy-based domain-specific languages (DSLs). It is a complete guide to the
development of several mini DSLs with a lot of easy-to-understand examples.
This book will help you gain all of the skills needed to develop your own
Groovy-based DSLs.
Groovy for Domain-specific Languages, Second Edition, guides you from the basics
through to the more complex metaprogramming features of Groovy. The focus is on
how the Groovy language can be used to construct domain-specific mini languages.
Practical examples are used throughout to demystify these seemingly complex
language features and to show how they can be used to create simple and elegant
DSLs. The examples include a quick and simple Groovy DSL to interface
with Twitter.
The book concludes with a chapter focusing on integrating a Groovy-based
DSL in such a way as the scripts can be readily incorporated into your own Java
applications. The overall goal of this book is to take developers through the skills
and knowledge they need to start building effective Groovy-based DSLs to integrate
into their own applications.
Preface
Preface
Chapter 10, Building a Builder, explains how Groovy provides two useful support
classes that make it much simpler to implement our own builders than if we used the
MOP. You'll see how to use BuilderSupport and FactoryBuilderSupport to create
our own builder classes.
Chapter 11, Implementing a Rules DSL, takes a look at Groovy bindings to see how
they can be used in our DSL scripts. By placing closures strategically in the binding,
you can emulate named blocks of code. You can also provide built-in methods and
other shorthand by including closures and named Boolean values in the binding.
These techniques can be used to great effect to write DSL scripts that can be read
and understood by stakeholders outside of the programming audience.
Chapter 12, Integrating It All, takes all the knowledge from the previous chapters and
builds a fully functioning web application based on a simple Game Engine DSL for
Tic Tac Toe.
Introduction to DSLs
and Groovy
It has been over 10 years since my first contact with the Groovy language. The
occasion was an introductory talk about Groovy at JavaOne in the Moscone Centre,
San Francisco, by James Strachan, the creator of the Groovy language. Java itself was
just 10 years old at that time and Groovy was one of the very first languages other
than Java to run on the Java Virtual Machine (JVM).
Just this year, Java celebrated its twentieth birthday. In 2005, there were an estimated
3 million Java developers worldwide. Now, in 2015, Wikipedia estimates it as 11
million. The Groovy language has also taken off. There were an estimated 5 million
downloads of Groovy in the last year alone. So what are the benefits of Groovy and
why should you consider using it?
The Groovy project site at http://www.groovy-lang.org explains this better than I
ever could with six major benefits:
Powerful features
Domain-specific languages
In this book, we will cover all the key benefits of the Groovy language. The main
focus, however, is on how Groovy supports the development of domain-specific
languages through its metaprogramming features.
[1]
One of the big benefits of Groovy is how its dynamic features support the
development of domain-specific languages (DSLs) or "mini languages", which
we can run directly on the JVM alongside your existing Java code. Groovy DSLs
integrate seamlessly into the Groovy language itself in such a way that it's not
always apparent where the regular Groovy code stops and the DSL starts.
In fact, large parts of almost any Groovy application are written using Groovy-based
DSLs. For instance, a new developer starting out with Groovy might assume that
the builder code he uses to output some XML is a part of the core Groovy
language. But it is, in fact, a mini internal DSL implemented using the Groovy
metaprogramming features.
If you are an Android developer, the chances are you may have programmed
in Groovy already. Since 2013, the build system in the Android SDK has been a
tool called Gradle (http://www.gradle.org). Gradle is a Groovy-based DSL for
dependency management and build automation.
Whether you are one of the 11 million existing Java developers, looking to add
DSL features to you application, or you are an existing Groovy developer looking
to improve your knowledge of DSL writing, metaobject programming or AST
transformations, this book is intended for you.
By the end of this book, I hope that you will have the knowledge and the confidence
to start building your own DSLs with Groovy, and be able to integrate them into
your Java applications. To begin with, in this chapter, we will take some baby steps.
This chapter will give you a brief background on DSLs and their usage. We will also
dip a toe into the Groovy language, and briefly touch on the features of the language
that distinguish it from Java and make it a great tool for developing DSLs on top of
the Java platform.
[2]
Chapter 1
The Java platform has a multitude of mini DSLs in the form of XML config files for
configuration of everything from EJBs to web applications. In many JEE applications,
Enterprise Java Beans (EJB) can be configured using an XML configuration file,
ejb-jar.xml. While the ejb-jar.xml file is written in the general-purpose language
XML, the contents of the file need to conform to a document type definition (DTD)
or XML schema, which describes the valid structure of the file.
XML configuration files can be found across a wide range of libraries and
frameworks. Spring is configured by using a spring-config.xml file, and Struts
with struts-config.xml. In each case, the DTD or schema defines the elements
and tags, which are valid for the specific domain, be it EJB, Spring, or Struts. So,
ejb-jar.xml can be considered a mini DSL for configuring EJB, spring-config.
xml is a mini DSL for configuring Spring beans, and so on.
In essence, DSL is a fancy name for something that we use every day of our
professional programming lives. There are not many applications that can be
fully written in a single general-purpose language. As such, we are the everyday
consumers of many different DSLs, each of which is specific to a particular purpose.
A typical day's work could involve working with Java code for program logic, CSS
for styling a web page, JavaScript for providing some dynamic web content, and
Ant, Maven, or Gradle to build the scripts that tie it all together. We are well used
to consuming DSLs, but seldom consider producing new DSLs to implement our
applicationswhich we should.
[3]
The IOCCC runs to this day. The point of the contest is to write valid but
impenetrable C code that works. Check out http://www.ioccc.org to
see how not to write code.
General-purpose languages
All of the teaching in college in those days revolved around the general-purpose
languages. I recall sitting in class and being taught about the "two" types of
programming language: machine language, and high-level languages. Both
were types of general-purpose languages, in which you could build any type of
application, but each language had its own strengths and weaknesses. The notion of
a DSL was not yet considered as part of the teaching program. Nor was the idea that
anyone other than a cadre of trained professional programmers (hackers) would ever
write programs for computers. These days, the word "hacker" has bad connotations
of being synonymous with virus writers and the likes. In those days, a good "hack"
was an elegant programming solution to a hard problem and being called a hacker
by one's peers was a badge of pride for most programmers.
The high-level programming language you used defined what type of an application
programmer you were. COBOL was for business application programming, Fortran
was for scientific programmers, and C was for hackers building Unix and PC
software. Although COBOL and Fortran were designed to be used in a particular
business domain, they were still considered general-purpose languages. You could
still write a scientific application in COBOL or a business application in Fortran
if you wanted to. However, you were unlikely to try any low-level device driver
development in COBOL.
Although it was possible to build entire applications in assembly language (and
many people did), high-level languages, such as C, BASIC, and COBOL, were
much better suited to this task. The first version of the world-beating spreadsheet
Lotus 1-2-3 was written entirely in 8086 assembly language, and ironically, it was
the rewrite of this into the supposed high-level language C that nearly broke the
company in the late 1980's.
Languages such as C and C++ provide the low-level functionality in a high-level
language, which enabled them to be used across a much greater range of domains,
including those where assembly was utilized before. These days, Java, C# and C++
compete with each other like the Swiss Army knives of general-purpose languages.
There are almost no application domains to which these languages have not
been applied, from space exploration, through to enterprise business systems,
and mobile phones.
[4]
Chapter 1
Language-oriented programming
Martin Fowler has spoken about the use of many mini DSLs in application
development. He advocates building applications out of many mini DSLs, which
are specific to the particular problem space, in a style of development called
language-oriented programming. In a way, this style of programming is the norm
for most developers these days, when we mix and match HTML, CSS, SQL, and Java
together to build our applications.
[5]
[6]
Chapter 1
CPI developed a process control system, which was primarily sold to chemical and
pharmaceutical industries. It was a genuinely distributed system when most process
control systems were based on centralized mini or mainframe computers. It had its
own real-time kernel, graphics, and a multitude of device drivers for all types of
control and measurement devices. But the most innovative part of the system, which
excited customers, was a scripting language called EXTended Operations Language
(EXTOL). EXTOL was a DSL in the purest sense because it drew the domain experts
right into the development process, as originators of the running code.
With EXTOL, a chemical process engineer or chemist could write simple scripts to
define the logic for controlling their plant. Each control block and measurement
block in the system was addressable from EXTOL. Using EXTOL, a process engineer
could write control logic in the same pseudo English that they used to describe the
logic to their peers.
The following script could be deployed on a reactor vessel to control the act of
half-filling the vessel with the reactant from VALVE001:
drive VALVE001 to OPEN
when LEVELSENSOR.level >= 50%
drive VALVE001 to CLOSED
This was an incredibly powerful concept. Up to this point, most process control
systems were programmed in a combination of high-level languages on the main
process system, and relay logic on PLCs in the plant. Both tasks required specific
programming skills, and could not generally be completed by the chemists or
chemical engineers, who designed the high-level chemical processing undertaken at
the plant. I recall a room full of white-coated chemists at one plant happily writing
EXTOL scripts, as we commissioned the plant.
The proof of the pudding is always in the eating, and I don't recall a CPI engineer
ever being called upon to write a single line of EXTOL code on behalf of a customer.
Given an appropriate DSL that fit their needs, our customers could write all of the
code that they need themselves, without having to be programmers.
This shows the power of DSLs at their best. At this extreme end of the spectrum,
a DSL becomes a programming tool that a domain expert can use independently,
and without recourse to the professional programmer. It's important to remember,
however, that the domain experts in this case were mostly process engineers.
Process engineers are already well accustomed to devising stepwise instructions,
and building process flows. They will often use the same visual representations as
a programmer, such as a flow chart to express a process that they are working on.
[7]
When devising a DSL for a particular domain, we should always consider the
stakeholders who need to be involved in using it. In the case of EXTOL, the DSL
was targeted at a technical audience who could take the DSL and become part of the
system development process. Not all of our stakeholders will be quite as technical as
this. But, at the very least, the goal when designing a DSL should be to make the DSL
understandable to nontechnical stakeholders.
Stakeholder participation
It's an unfortunate fact that with many DSLs, especially those based on XML,
the code that represents a particular domain problem is often only legible to the
programming staff. This leads to a disconnect between what the business analysts
and domain experts define, and what eventually gets implemented in the system. For
instance, a business rule is most likely to be described in plain English by a business
analyst in a functional specification document. But these rules will most likely be
translated by developers into an XML representation that is specific to the particular
rules engine, which is then deployed as a part of the application. If the business
analyst can't read the XML representation and understand it, then the original intent
of the rule can easily be lost in translation.
With language-oriented programming, we should aim to build DSLs that can be read
and understood by all stakeholders. As such, these DSLs should become the shared
living specification of the system, even if in the end they must, by necessity, be
written by a programmer with a technical understanding of the DSL.
[8]
Chapter 1
EXTOL circumvented this problem by having its own syntax-sensitive editor. Users
edited their EXTOL scripts from within the editor, and were prompted for the
language constructs that they needed to use for each circumstance. This ensured
that the scripts were always well-formed and syntactically correct. This also meant
that the editor can save the scripts in an intermediate p-code form so that the
scripts never existed as text-based program files, and therefore never needed
to be compiled.
Many of the DSLs that we use are embedded within other languages. The multitude
of XML configuration scripts in the Java platform are an example of this. These mini
DSLs piggyback on the XML syntax, and can optionally use an XML DTD or schema
definition to define their own particular syntax. These XML-based DSLs can be easily
validated for "well-formedness" by using the DTD or schema.
[9]
Operator overloading
Some general-purpose languages, such as C++, Lisp, and now Groovy, have
language features that assist in the development of mini language syntaxes. C++ was
one of the earliest languages to implement the concept of operator overloading. By
using operator overloading, we can make non-numeric objects behave like numeric
values by implementing the appropriate operators. So, we can add a plus operator to
a String object in order to support concatenation. When we implement a class that
represents a numeric type, we can add the numeric operators again to make them
behave like numeric primitives. We can implement a ComplexNumber class, which
represents complex numbers, as follows:
class ComplexNumber {
public:
double real, imag;
ComplexNumber() { real = imag = 0; }
ComplexNumber(double r, double i) { real = r; imag = i; }
ComplexNumber& operator+(const ComplexNumber& num);
};
To add one complex number to another, we need to correctly add each of the real
and imaginary parts together to generate the result. We implement an equality
operator for ComplexNumber as follows:
ComplexNumber& ComplexNumber::operator=(const ComplexNumber& num) {
real = num.real;
imag = num.imag;
return *this;
}
This allows us then to add ComplexNumber objects together as if they were simple
numeric values:
int main(int argc, const char* argv[]) {
ComplexNumber a(1, 2), b(3, 4);
ComplexNumber sum;
sum = a + b;
cout << "sum is " << sum.real << " ; "
<< sum.imaginary << "i" << endl;
}
One of the criticisms of the operator overload feature in C++ is that when using
operator overloading, there is no way to control what functionality is being
implemented in the overloaded function. It is perfectly possiblebut not very
sensibleto make the + operator subtract values and the operator add values.
Misused operator overloading has the effect of obfuscating the code rather than
simplifying it. However, sometimes this very obfuscation can be used to good effect.
[ 10 ]
Chapter 1
Groovy
In the later chapters of this book, we will discuss the Groovy language in detail,
but let's begin with a brief introduction to the language and some of the features
that make it a useful addition to the Java platform.
The Java platform has expanded over the years to cover almost all conceivable
application nichesfrom Enterprise applications, to mobile and embedded
applications. The core strengths of Java are its rich set of APIs across all of these
problem domains and its standardized virtual machine (VM) interface. The
standard VM interface has meant that the promise of "write once, run anywhere" has
become a reality. The JVM has been implemented on every hardware architecture
and operating system from the mightiest mainframe down to the humble Lego
Mindstorms robotic kits for kids.
[ 11 ]
On top of this standard VM, the list of APIs that have been built extends into every
conceivable domain. In addition to the standard APIs that are a part of JME, JSE, and
JEE, which are extensive in themselves, there are literally thousands of open source
component libraries and tools to choose from. All of this makes for a compelling
argument for using Java for almost any software project that you can think of.
For many years of its evolution, the JVM was considered to be just thata virtual
machine for running Java programs. The JVM spec was designed originally by James
Gosling to be used exclusively for the Java language. In recent years, there have been
a number of open source projects that have started to introduce new languages on
top of the JVM, such as JRuby (an implementation of the Ruby language), Jython (an
implementation of the Python language and Groovy), Clojure, and Scala.
[ 12 ]
Chapter 1
Both variables str1 and str2 are of the type String. The late binding of the type in
the Groovy-style assignment allows for a much less verbose code.
[ 13 ]
Closures
Closures are one of the most powerful language features in Groovy. Closures are
anonymous code fragments that can be assigned to a variable. Closures can be
invoked by the call method as follows:
def biggest = { number1, number2 ->
number1<number2?number2:number1
}
// We can invoke the call method of the Closure class
def result = biggest.call(7, 1)
println result
// We can use the closure reference as if it were a method
result = biggest(3, 5)
println result
// And with optional parenthesis
result = biggest 13, 1
println result
Closures can contain multiple statements and can therefore be as complex as you
like. In the following example, we iterate through a list looking for the biggest
number, and return it when we are done:
def listBiggest = { list ->
def biggest = list[0]
for( i in list)
if( i > biggest)
biggest = i
return biggest
}
def numberList = [ 8, 6, 7, 5, 3, 9]
println listBiggest( numberList)
[ 14 ]
Chapter 1
Optional syntax
Optional typing means that variable type annotations are optional. This does not
mean that variables have an unknown variable type. It means that the type will be
determined at run time based on the value that gets assigned to the variable. All of
the following are legal syntax in Groovy:
int a = 3
def b = 2
String t = "hello"
def s = 'there'
Trailing semicolons at the end of statements are optional. The only time that you
explicitly need to use a semicolon in Groovy is to separate statements that occur on
the same line of code, as shown in the first and third lines in the following code:
int a = 3; int b = 4;
def c = 2
def d = 5; def e = 6
[ 15 ]
Method call parentheses are also optional when the method being invoked has
passed some parameters. We saw earlier, with closures, that we can invoke a closure
through its reference as if it were a method call. When invoking a closure in this
way, we can also drop the parentheses when passing parameters, as shown in the
following code:
println( a );
c = 2
print c
printit = { println it }
printit c
These make for a much looser programming style, which is closer to the scripting
syntax of Ruby or Python. This is a big benefit when we are using Groovy to build
DSLs. When our target audience is nontechnical, being able to drop parentheses and
semicolons will make our code much more legible. Consider the following example,
where we have two methods, or closures, to get an account by ID and then credit the
account with some funds:
Account account = getAccountById( 234 );
creditAccount( account, 100.00 );
With optional types, such as parentheses and semicolons, this can be used to write
code that is far more legible to our target audience:
account = getAccountById 234
creditAccount account, 100.00
Groovy markup
There are a number of builder classes built in Groovy. There are markup builders for
HTML, XML, Ant build scripts, and for Swing GUI building. Markup builders allow
us to write code to build a tree-based structure directly within our Groovy code.
Unlike API-based approaches for building structures, the tree-like structure of the
resulting output is immediately obvious from the structure of our Groovy markup
code. Consider the following XML structure:
<?xml version="1.0"?>
<book>
<author>Fergal Dearle</author>
<title>Groovy for DSL</title>
</book>
[ 16 ]
Chapter 1
In Groovy markup, this XML can be generated simply with the following
code fragment:
def builder = new groovy.xml.MarkupBuilder()
builder.book {
author 'Fergal Dearle'
title 'Groovy for DSL'
}
At first glance, this looks like strange special case syntax for markup. It's not! The
structure of this code can be explained through the use of closures and the optional
syntax that we've discussed in this chapter. We will go into this in great detail in
Chapter 5, Groovy Closures, but it is interesting at this point to see how the clever
use of some language features can yield a powerful DSL-like markup syntax.
Breaking down the preceding code a little, we can rewrite it as:
def builder = new groovy.xml.MarkupBuilder()
def closure = {
author 'Fergal Dearle'
title 'Groovy for DSL'
}
// pass a closure to book method
builder.book( closure)
// which can be written without parentheses
builder.book closure
// or just inline the closure as a parameter
builder.book {
In other words, the code between the curly braces is in fact a closure, which is passed
to the book method of MarkupBuilder. Parentheses being optional, we can simply
declare the closure inline after the method name, which gives the neat effect of
seeming to mirror the markup structure that we expect in the output.
Similarly, author and title are just method invocations on MarkupBuilder with
the optional parentheses missing. Extending this paradigm a little further, we can
decide to have author take a closure parameter as well:
def builder = new groovy.xml.MarkupBuilder()
builder.book {
author {
[ 17 ]
The method calls on MarkupBuilder start off by outputting an opening XML tag,
after which they invoke the closure if one has been passed. Finally, the XML tag
is properly terminated before the method exits. If we analyze what happens in
sequence, we can see that book invokes a closure that contains a call to author.
Additionally, the author tag contains a closure with calls to first_name, surname,
and so on.
Before you go to the Groovy documentation for MarkupBuilder to look for the book,
author, and surname methods in MarkupBuilder, let me save you the effort. They
don't exist. These are what we call pretend methods. We will see later in the book
how Groovy's metaprogramming features allow us to invoke methods on closure
that don't really exist, but have them do something useful anyway.
Already, we are seeing how some of the features of the Groovy language can
coalesce to allow the structuring of a very useful DSL. I use the term DSL here for
Groovy builders because that is essentially what they are. What initially looks like
special language syntax for markup is revealed as being regular closures with a
little bit of clever metaprogramming. The result is an embedded or internal DSL
for generating markup.
[ 18 ]
Chapter 1
Summary
So, now we have a feel for DSLs and Groovy. We have seen how DSLs can be used in
place of general-purpose languages to represent different parts of a system. We have
also seen how adding DSLs to our applications can open up the development process
to other stakeholders in the development process. We've also seen how, in extreme
cases, the stakeholders themselves can even become co-developers of the system by
using DSLs that let them represent their domain expertise in code.
We've seen how using a DSL that makes sense to a nontechnical audience means
it can become a shared resource between programming staff and business
stakeholders, representing parts of the system in a language that they all
understand. So, we are beginning to understand the importance of usability
when designing a DSL.
We have dipped a tentative toe in the water by looking at some Groovy code. We've
gained an appreciation of how Groovy is a natural fit with the Java language due
to its binary and class level compatibility. We have touched on the features of the
Groovy language that make it unique from Java, and looked at how these unique
features can be used as a basis for building on the base Groovy language with
internal DSLs.
In the next chapter, we will go into more depth with the language itself and see how
we can use these features to build programs. In subsequent chapters, we will dive
deeper and see how the language can be exploited as an ideal platform for building
DSLs on top of the Java platform.
[ 19 ]
www.PacktPub.com
Stay Connected: