Contents


Evolutionary architecture and emergent design

Building DSLs in Groovy

More-expressive harvesting of idiomatic patterns

Comments

Content series:

This content is part # of # in the series: Evolutionary architecture and emergent design

Stay tuned for additional content in this series.

This content is part of the series:Evolutionary architecture and emergent design

Stay tuned for additional content in this series.

In last month's installment, I showed examples of using domain-specific languages (DSLs) to harvest idiomatic patterns, defined as common design idioms in your code. (I introduced the concept of idiomatic patterns in "Composed method and SLAP.") DSLs are a good medium for capturing patterns because they are declarative, are more readable than "normal" source code, and allow your harvested patterns to stand out from the surrounding code.

Language techniques for constructing DSLs frequently use clever tricks to supply wrapping context for your code implicitly. In other words, DSLs try to "hide" noisy syntax using features of the underlying language to make your code more readable. Although you can build DSLs in the Java language, its anemic set of constructs for hiding context, along with its rigid and unforgiving syntax, make it ill-suited to this technique. But other JVM-based languages can fill the gap. In this installment and the next one, I'll show how you can expand your DSL-building palette to include more-expressive languages that run on the Java platform, starting with Groovy (see Related topics).

Groovy offers several features that make building DSLs easier. Support for quantities is a common requirement in DSLs. People always need numbers of things: 7 inches, 4 miles, 13 days. Groovy allows you to add direct support for numeric quantities via open classes. Open classes allow you to reopen existing classes and make changes to them by adding, removing, or changing methods in the class — a mechanism that's both powerful and dangerous. Fortunately, safe ways exist to implement this power. Groovy supports two different syntaxes for open classes: categories and the ExpandoMetaClass.

Open classes via categories

The concept of categories is borrowed from languages like Smalltalk and Objective-C (see Related topics). A category creates a wrapper around the invocation of code that contains one or more open classes, using the use block directive.

Categories are best understood through an example. Listing 1 shows a test that demonstrates a new method I've added to String called camelize(), which converts underscore-delimited strings into camel case:

Listing 1. Test demonstrating the camelize() method
class TestStringCategory extends GroovyTestCase {
    def expected = ["event_map" : "eventMap", 
            "name" : "name", "test_date" : "testDate", 
            "test_string_with_lots_of_breaks" : "testStringWithLotsOfBreaks",
            "String_that_has_init_cap" : "stringThatHasInitCap" ]

    void test_Camelize() {
        use (StringCategory) {
            expected.each { key, value ->
                assertEquals value, key.camelize()
            }
        }
    }
}

In Listing 1, I create an expected hash with the original and transformed cases, then wrap the StringCategory around the iteration over the map, expecting each of the keys to become camelized. Notice that within the use block, you don't need to do anything special to call the new method(s) on the class.

The code for StringCategory appears in Listing 2:

Listing 2. The StringCategory class
class StringCategory {

  static String camelize(String self) {
    def newName = self.split("_").collect() { 
      it.substring(0, 1).toUpperCase() +  it.substring(1, it.length())
    }.join()
    newName.substring(0, 1).toLowerCase() +  newName.substring(1, newName.length())      
  }
}

Categories are regular classes containing static methods. The static method must have at least one parameter, which is the type you are augmenting. In Listing 2, I declare a single static method that accepts a String parameter (traditionally called self, but you can call it whatever you like) that represents the class I'm adding the method to. The body of the method contains Groovy code to break the string up into chunks delimited by the underscore (that's what the split("_") method does), then collects the strings back together and joins the pieces with capitals in place. The last line handles making sure the first character of the returned string is lowercase.

When you use the StringCategory, you must access it within a use block. It is legal to have multiple category classes, separated by commas, within the use block's parentheses.

Here is another example of using open classes to express quantities in a DSL. Consider the code in Listing 3, which implements a simple appointment calendar:

Listing 3. A simple calendar DSL
def calendar = new AppointmentCalendar()

use (IntegerWithTimeSupport) {
    calendar.add new Appointment("Dentist").from(4.pm)
    calendar.add new Appointment("Conference call")
                 .from(5.pm)
                 .to(6.pm)
                 .at("555-123-4321")
}
calendar.print()

Listing 3 implements the same kind of functionality as the Java examples in "Fluent interfaces" but with enhanced syntax, including several things that aren't possible in Java code. For example, notice that Groovy allows me to omit parentheses in some places (like the ones around the argument to the add() method). I can also make calls like 5.pm, which looks odd to Java developers. This is an example of opening the Integer class (all numbers in Groovy use the type-wrapper classes automatically, so even 5 is really an Integer) and adding a pm property. The class that implements this open class appears in Listing 4:

Listing 4. IntegerWithTimeSupport class definition
class IntegerWithTimeSupport {
    static Calendar getFromToday(Integer self) {
        def target = Calendar.instance
        target.roll(Calendar.DAY_OF_MONTH, self)
        return target
    }

    static Integer getAm(Integer self) {
        self == 12 ? 0 : self
    }

    static Integer getPm(Integer self) {
        self == 12 ? 12 : self + 12
    }
}

This category class includes three new methods for Integer: getFromToday(), getAm(), and getPm(). Notice that these are actually new properties, not methods. The reason I wrote them as new properties has to do with how Groovy handles method invocation. When you invoke a Groovy method that has no parameters, you must call it with an empty set of parentheses, which allows Groovy to distinguish between a property access from a method call. If I wrote the extensions as methods, my DSL would need to call the am and pm extensions as 5.pm(), which harms the readability of the DSL. One of the main reasons I'm using the DSL is to enhance readability, so I would like to get rid of extra noise. You can do this in Groovy by creating the extensions as properties instead. The syntax for declaring properties is the same as in the Java language — with a pair of get/set methods — but you can call them without parentheses.

In this DSL, the unit of measurement is hours, which means that I need to return 15 for 3.pm. When building DSLs that feature quantities, you must decide on your units and (optionally) add them to the DSL to make it more readable. Remember that I'm using the DSL to capture a domain idiomatic pattern, which means that nondevelopers might read it.

Now that you've seen how to implement time in the calendar DSL, the Appointment class, shown in Listing 5, is straightforward:

Listing 5. The Appointment class
class Appointment {
  def name;
  def location;
  def date;
  def startTime;
  def endTime;

  Appointment(apptName) {
    name = apptName
    date = Calendar.instance
  }

  def at(loc)  {
    location = loc
    this
  }

  def formatTime(time) {
    time > 12 ? "${time - 12} PM" : "${time} AM"
  }

  def getStartTime() {
    formatTime(startTime)
  }

  def getEndTime() {
    formatTime(endTime)
  }

  def from(start_time) {
    startTime = start_time
    date.set(Calendar.HOUR_OF_DAY, start_time)
    this
  }

  def to(end_time) {
    endTime = end_time
    date.set(Calendar.HOUR_OF_DAY, end_time)
    this
  }

  def display() {
    print "Appointment: ${name}, Starts: ${formatTime(startTime)}"
    if (endTime) print ", Ends: ${formatTime(endTime)}"
    if (location) print ", Location: ${location}"
    println()
  }
}

Even if you don't know any Groovy, you probably won't have any trouble reading the Appointment class. Note that in Groovy, the method's last line is its return value. This makes the last line of the at(), from(), and to() methods (the return of this) the fluent-interface calls in this class.

Categories allow you to make changes to existing classes in a controlled way. The changes are strictly scoped to the lexical block defined by the use() clause. However, there are times when you want an open class's added methods to have broader scope, which is where Groovy's ExpandoMetaClass helps.

Open classes via expando

The original open-class syntax in Groovy used categories only. However, the creators of the Groovy web framework, Grails (see Related topics), found the scoping inherent in categories too restrictive. This led to the development of an alternative syntax for open classes, the ExpandoMetaClass. When using an expando, you access the class's metaclass (which Groovy opportunistically creates for you) and add properties and methods to it. The calendar example using expandos appears in Listing 6:

Listing 6. Calendars with expando open classes
def calendar = new AppointmentCalendar()

calendar.add new Appointment("Dentist")
             .from(4.pm)
calendar.add new Appointment("Conference call")
             .from(5.pm)
             .to(6.pm)
             .at("555-123-4321")
        
calendar.print()

The code in Listing 6 looks almost the same as in Listing 3, minus the use block required for categories. To implement the changes to Integer, you access the metaclass as shown in Listing 7:

Listing 7. Expando definitions for Integer
Integer.metaClass.getAm = { ->
  delegate == 12 ? 0 : delegate
}                              

Integer.metaClass.getPm = { ->
  delegate == 12 ? 12 : delegate + 12
}                                

Integer.metaClass.getFromToday = { ->
  def target = Calendar.instance
  target.roll(Calendar.DAY_OF_MONTH, delegate)
  target
}

As in the category example, I need am and pm as properties rather than methods (so that I won't have to access them with parentheses when I call them) on Integer, so I add a new property to the metaclass as Integer.metaClass.getAm. These code blocks can accept parameters, but I don't need them here (thus the lone -> at the beginning of the code block). Within the code block, the delegate keyword refers to the instance of the class you are adding methods to. For example, notice that in the getFromToday property, I create a new Calendar instance, then use the delegate value to roll the calendar the number of days specified by this instance of Integer. When I execute 5.fromToday, I'm rolling the calendar ahead five days.

Choosing between categories and expando

Given that categories and expandos give you the same kind of expressiveness, which should you choose? The nicest thing about categories is the inherent scope limiting of the lexical block. It is a common DSL antipattern to make fundamental (possibly breaking) changes to core classes of the language. Categories force a limit to modifications. Expandos, on the other hand, are global in nature: Once the expando code has executed, those changes appear for the rest of the application.

In general, prefer categories. When making changes to important classes with potential side effects, you want to limit the scope of those changes. Categories allow you to scope changes narrowly. However, if you find yourself wrapping more and more code with the same categories, you should escalate to expandos. Some changes need to be broad, and forcing all those changes to fit within blocks can lead to convoluted code. As a rule of thumb, if you find yourself wrapping more than three disparate chunks of code in a category, consider making it an expando.

One last note: testing isn't optional here. Lots of developers seem to think that testing is optional for large swaths of their code, but any code that makes changes to existing classes requires comprehensive testing. The capability to modify core classes is powerful and can lead to elegant solutions to problems. But with power comes responsibility, which manifests as tests.

Occurrences in the wild

This discussion of DSLs as a way to capture idiomatic patterns may seem a bit abstract up until now, so I'll finish with an example from the real world.

easyb (see Related topics) is a Groovy-based behavior-driven development testing tool that allows you to create scenarios that combine nondeveloper-friendly prose with code to implement a test. An example easyb scenario appears in Listing 8:

Listing 8. easyb scenario testing a queue
package org.easyb.bdd.specification.queue

import org.easyb.bdd.Queue

description "This is how a Queue must work"

before "initialize the queue for each spec", {
    queue = new Queue()
}

it "should dequeue item just enqueued", {
    queue.enqueue(2)
    queue.dequeue().shouldBe(2)
}

it "should throw an exception when null is enqueued", {
    ensureThrows(RuntimeException.class) {
        queue.enqueue(null)
    }
}

it "should dequeue items in same order enqueued", {
    [1..5].each {val ->
        queue.enqueue(val)
    }
    [1..5].each {val ->
        queue.dequeue().shouldBe(val)
    }
}

The code in Listing 8 defines proper behavior for a queue. Each of the declaration blocks starts with it, followed by a string description and a code block. The method definition for it looks like this, where spec is expected to describe the test and closure holds the code block:

def it(spec, closure)

Note that in the last test in Listing 8, I'm verifying the value that comes from the call to dequeue(), using this line of code:

queue.dequeue().shouldBe(val)

But inspection of the Queue class shows that it does not have a shouldBe() method. Where did it come from?

If you look at the definition of the it() method, you can see where categories are used to augment existing classes. Listing 9 shows the it() method's declaration:

Listing 9. Declaration of the it() method
def it(spec, closure) {
    stepStack.startStep(listener, BehaviorStepType.IT, spec)
    closure.delegate = new EnsuringDelegate()
    try {
        if (beforeIt != null) {
            beforeIt()
        }
        listener.gotResult(new Result(Result.SUCCEEDED))
    use(BehaviorCategory) {
()
        }
        if (afterIt != null) {
            afterIt()
        }
    } catch (Throwable ex) {
        listener.gotResult(new Result(ex))
    }
    stepStack.stopStep(listener)
}

At about the halfway point of the method, the closure block passed as the parameter is executed within the BehaviorCategory class, an excerpt of which appears in Listing 10:

Listing 10. Portion of the BehaviorCategory class
static void shouldBe(Object self, value, String msg) {
    isEqual(self, value, msg)
}

private static void isEqual(self, value, String msg) {
    if (self.getClass() == NullObject.class) {
        if (value != null) {
            throwValidationException(
                "expected ${value.toString()} but target object is null", msg)
        }
    } else if (value.getClass() == String.class) {
        if (!value.toString().equals(self.toString())) {
            throwValidationException(
                "expected ${value.toString()} but was ${self.toString()}", msg)
        }
    } else {
        if (value != self) {
            throwValidationException("expected ${value} but was ${self}", msg)
        }
    }
}

BehaviorCategory is a category whose methods augment Object, which illustrates the incredible power of open classes. By adding a new method to Object, you give every instance in the application access to those methods, making it trivial to add a shouldBe() method to every class (including Queue). You cannot do this using core Java code, and it would be cumbersome to do it even with aspects. The use of categories reinforces my previous recommendation: it limits the scope of changes to Object to the body of the use clause in the easyb DSL.

Conclusion

I want the idiomatic patterns that I harvest to stand out from the rest of my code, and DSLs provide a compelling mechanism for achieving this goal. It's much easier to write DSLs in languages that have support for writing them, unlike the Java language. If external factors within your organization prevent you from taking advantage of non-Java languages, don't give up. Tools like the Spring framework have more and more support for alternative languages such as Groovy or Clojure (see Related topics). You can use these languages to create components and let Spring inject them into the appropriate places in your application. Many organizations are overly conservative about alternative languages, but an easy incremental route exists through frameworks like Spring.

In the next installment, I'll wrap up the topic of using DSLs as a way to harvest domain idiomatic patterns with some examples in JRuby, illustrating just how far you can push languages toward expressiveness.


Downloadable resources


Related topics

  • The Productive Programmer (Neal Ford, O'Reilly Media, 2008): Neal Ford's most recent book expands on a number of the topics in this series.
  • Practically Groovy: This developerWorks series explores the practical uses of Groovy, helping you learn when and how to apply them successfully.
  • Grails: Grails is the web framework written atop Groovy, inspired by Ruby on Rails.
  • Mastering Grails: Dig deeply into Grails in this developerWorks article series.
  • easyb: easyb is a behavior-driven development testing tool implemented in Groovy that uses many of the techniques covered in this installment.
  • Clojure: Clojure is a modern dialect of Lisp recast as a purely functional language that runs on the JVM.
  • Objective_C: This Wikipedia article discusses the origins of categories, with examples.

Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java development
ArticleID=509487
ArticleTitle=Evolutionary architecture and emergent design: Building DSLs in Groovy
publish-date=08172010