In the previous part The Essence: The closures’ basics we have shown how to use built-in Groovy methods to create a prototype of our…


Groovy DSL Builders #3: The Aid

In the previous part The Essence: The closures’ basics we have shown how to use built-in Groovy methods to create a prototype of our builder DSL. In this post, we are going to implement our own closures’ handlers which provides support for static compilation.

YUML.me Diagram’s Diagram


Why should we actually bother about static compilation in Groovy? First, it runs faster and gives you another level of confidence but the main reason is that as a simple rule of thumb:

If the code can be compiled statically the IDE will understand your code and it will give you all the expected hints.

Although Cédric Champeau already implemented static compilation into Groovy language years ago, a lot of developers are ignoring this feature so far.

When developing DSL builders in Groovy there are three key parts which can increase the developers’ experience when our method accepts Closure:

Let’s take a look on another iteration of our YUML DSL:

Diagram.build {

    note('You can stick notes on diagrams too!', 'skyblue')

    aggregation('Customer', 'Order') {
        source '1'        destination '0..\*', 'orders'    }

    composition('Order', 'LineItem') {
        source '\*'        destination '\*'    }

    association('Order', 'DeliveryMethod') {
        destination '1'    }

    association('Order', 'Product') {
        source '\*'        destination '\*'    }

    association('Category', 'Product') {
        bidirectional true    }

    type 'National' inherits _from_ type 'DeliveryMethod'    type'International' inherits _from_ type 'DeliveryMethod'
}

The latest iteration of the DSL uses two new features

  1. Tree-like structure builder DSL using Groovy closures
  2. Fluent DSL using command chain expressions

Methods note and relationship are now accepting closures as the last argument which allows us to place them out of the method call brackets. The closure arguments are annotated withDelegatesTo annotation to give hints for the static compiler. They still use withmethod internally:

Relationship relationship(
String source,
RelationshipType relationshipType,
String destination,
@DelegatesTo(
value = Relationship,

        strategy = Closure.\1    )
    Closure additionalProperties = Closure._IDENTITY
_) {
    Relationship relationship = new Relationship(
    type(source),   
    relationshipType,   
    type(destination)  
)  
relationship.with additionalProperties  
    this.relationships.add(relationship)
    return relationship
}

The common mistake is to forget setting strategy to Closure.DELEGATE_FIRST as the default strategy of the DelegatesTo annotation is Closure.OWNER_FIRST.

Fluent part of the DSL uses some helper objects to keep the rhythm method-parameter-method-parameter -*. For example Type now provides method inherits which returnsInheritanceBuilder

InheritanceBuilder inherits(From from) {

    return new InheritanceBuilder(diagram, this)
}

The InheritanceBuilder class is pretty simple:

@CompileStatic
class InheritanceBuilder {

    private final Type source
    private final Diagram diagram    InheritanceBuilder(Diagram diagram, Type destination) {
        this.source = destination
        this.diagram = diagram
    }
Relationship type(String destination) {  
        return diagram.inheritance(source.name, destination)
    }
}

These helper classes always needs to keep some back reference to help to build the desired object.

The last pieces of the puzzle are methods in the Diagram class which return the keywords such as from:

static From getFrom() {
    return From._FROM
_}

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

In the next part The Disguise: Hiding the implementation of the builder API we are going to make the design of our builder DSL cleaner by separating the data and the definition part of the library.


Contents

  1. The Concept: The core concept of builders
  2. The Essence: The closures’ basics
3.  [The Aid: _Using the annotations for static compilation_](https://medium.com/p/df2e9a02557a)
  1. The Disguise: Hiding the implementation of the builder API
  2. The Desiccation: Keeping the code DRY
  3. The Expectations: The importance of handling closures’ owner properly
  4. The Extension: Designing your builder DSL for extendability
  5. The Resignation: Rewriting the Groovy DSL builder into Java
  6. The Navigation: Using the annotations for named parameters
  7. The Conclusion: The checklist for Groovy DSL builders’ authors