In the first part of this series Groovy DSL Builders #1: The Concept we got familiar with the builder pattern and various builders…


Groovy DSL Builders #2: The Essence

In the first part of this series The Concept: The core concept of builders we got familiar with the builder pattern and various builders provided by Groovy standard library. We also introduced our sample YUML builder library which is described by following object model:

YUML.me Diagram’s Diagram

This post is focused on closures in Groovy.


Closures are an essential part of any Groovy DSL builders. Contrary to their Java 8 lambda counterpart, they add another player into the game of method and property resolution, which is called a delegate.

The most common method which changes closure’s delegate is the method with which can be used on any object and which allows you to call methods of given object directly from within the closure definition without the need to specify the caller subject. See the following example which benefits from calling with on the Diagram instance:

Diagram diagram =  new Diagram().with {
    note('You can stick notes on diagrams too!','skyblue')
relationship(  
        type('Customer'),
        RelationshipType.\1,
        type('Order')
).with {  
        sourceCardinality = '1'        destinationTitle = 'orders'        destinationCardinality = '0..\*'    }
relationship(  
        type('Order'),
        RelationshipType.\1,
        type('LineItem')
).with {  
        sourceCardinality = '\*'        destinationCardinality = '\*'    }

    relationship(type('Order'), type('DeliveryMethod')).with {
        destinationCardinality = '1'    }

    relationship(type('Order'), type('Product')).with {
        sourceCardinality = '\*'        destinationCardinality = '\*'    }

    relationship(type('Category'), type('Product')).with {
        bidirectional = true    }
relationship(  
        type('National'),
        RelationshipType.\1,
        type('DeliveryMethod')
    )
relationship(  
        type('International'),
        RelationshipType.\1,
        type('DeliveryMethod')
    )
it  

}

We have added some useful methods for building notes, types and relationships directly to Diagram class and we can call them simply using the with method call. There is one drawback that we need to return self (the parameter it) as a last line of the closure block. Since Groovy 2.5.x there is a method tap doing exactly what we want without the need of returning it but at the time of writing, the current version of IntelliJ IDEA didn’t seem to fully support highlights for this method.

Here is the updated Diagram class which now manages more gracefully the types and relationships collections using the methods of similar name. This is a pretty common pattern in the Groovy builders’ DSLs.

@CompileStatic
@EqualsAndHashCode
class Diagram {

    Collection<Note> notes = new LinkedHashSet<>()
    Map<String, Type> types = \[:\].withDefault { key ->
        new Type(key.toString())
    }

    Collection<Relationship> relationships = new LinkedHashSet<>()

    Note note(String text, String color = null) {
        Note note = new Note(text, color)
        this.notes.add(note)
        return note
    }
Type type(String name) {  
        types\[name\]
    }
Relationship relationship(  
    Type source,   
        RelationshipType type = RelationshipType.\1,
    Type destination  
    ) {
        Relationship relationship = new Relationship(
        source,  
        type,   
        destination  
         )
        this.relationships.add(relationship)
        return relationship
    }

    @Override
String toString() {  
        // print diagram
    }

}

Calling the method type or relationship will either find the existing object of given name or it will create a new one and return it.


The code is available on GitHub under 02-closures tag:

git clone https://github.com/musketyr/yuml-dsl-builder.git
cd yuml-dsl-builder
git checkout 02-closures


We are going to implement our own closure handlers in the next part The Aid: Using the annotations for static compilation.


Contents

  1. The Concept: The core concept of builders
  2. The Essence: The closures’ basics
  3. The Aid: Using the annotations for static compilation
  4. The Disguise: Hiding the implementation of the builder API
  5. The Desiccation: Keeping the code DRY
  6. The Expectations: The importance of handling closures’ owner properly
  7. The Extension: Designing your builder DSL for extendability
  8. The Resignation: Rewriting the Groovy DSL builder into Java
  9. The Navigation: Using the annotations for named parameters
  10. The Conclusion: The checklist for Groovy DSL builders’ authors