In the previous post The Extension: Designing your builder DSL for extendability we provided an extension point to our builder DSL. In…


Groovy DSL Builders #8: The Resignation

In the previous post The Extension: Designing your builder DSL for extendability we provided an extension point to our builder DSL. In this part, we are going to rewrite the API of the DSL to Java to reach a broader audience.

YUML.me Diagram’s Diagram


Groovy is a good citizen in a JVM country but still, the Java developer community is many times bigger than the Groovy one. Luckily it is quite easy to adapt the Groovy DSL builder to Java builder although Java code will be always be more verbose.

Java interface Consumer can act as a solid replacement of Groovy’s Closure. Following example shows the DSL rewritten to Java using Consumer lambdas:

Diagram.create(d -> {

    d.note("You can stick notes on diagrams too!", "skyblue");

    d.aggregation("Customer", "Order", r -> {
        r.source("1");
        r.destination("0..\*", "orders");
    });

    d.composition("Order", "LineItem", r -> {
        r.source("\*");
        r.destination("\*");
    });

    d.association("Order", "DeliveryMethod", r -> {
        r.destination("1");
    });

    d.association("Order", "Product", r -> {
        r.source("\*");
        r.destination("\*");
    });

    d.association("Category", "Product", r -> {
        r.bidirectional(true);
    });

    d.type("National").inherits(_from_).type("DeliveryMethod");
    d.type("International").inherits(_from_).type("DeliveryMethod");
})

Builder interfaces are now having methods similar to the following one:

RelationshipDefinition relationship(
String source,
RelationshipType relationshipType,
String destination,
Consumer additionalProperties
);

We still want to support the old Groovy DSL we had before. Although we can simply use closures in place of Consumer or any other single abstract method type (SAM type) but we would lose the ability to specify the delegate.

I believe it is a good practise not to mix Java and Groovy parts of the DSL. As there are still lot of Java developers terrified by adding Groovy to the classpath we should ideally rewrite all parts to Java. Then we can provide the Groovy DSL as extension module in separate library. To keep the YUML example simple the extension module is still part of the same project.

We have already used extension modules for adding more functionality to the builder. Now we can use it to completely extract the methods using closures. Each of the methods using Consumer will have its counterpart in an extension class:

public static RelationshipDefinition relationship(
DiagramDefinition self,  
String source,  
RelationshipType relationshipType,  
String destination,  
    @DelegatesTo(
        value = RelationshipDefinition.class,
        strategy = Closure.\1    )
    @ClosureParams(
        value = SimpleType.class,
        options = "cz.orany.yuml.model.dsl.RelationshipDefinition"    )
    Closure<? extends DiagramContentDefinition> additionalProperties
) {
    return self.relationship(
    source,  
    relationshipType,  
    destination,  
    ConsumerWithDelegate._create_(additionalProperties)  
);  

}

ConsumerWithDelegate class comes from Groovy Closure Support library which I had developed to ease the development of the builder DSLs which are primarily written in Java but they provide the best developer experience in Groovy. ConsumerWithDelegate and its function counterpart FunctionWithDelegate also handles gracefully setting the proper owner of the closure as discussed previously in The Expectations: The importance of handling closures’ owner properly.

You also need to change the way how the keywords are introduced into the DSL. Previously the keywords have been provided as static getters on the interfaces but this won’t be very practical from the Java code. The easiest possible solution is to extract all the keywords into a single holder class which we can import as static import.

public class DiagramKeywords {

    public static final From _from_ = From.\1;
    public static final Integer _zero_ = 0;
    public static final Integer _one_ = 1;
    public static final String _many_ = "\*";

}

The code is available on GitHub under 08-java-dsl tag:

git clone https://github.com/musketyr/yuml-dsl-builder.git
cd yuml-dsl-builder
git checkout 08-java-dsl


In the next part The Navigation: Using the annotations for named parameters we take a look at the new feature in Groovy 2.5 which is the support for type checking of the named method parameters.


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_](https://medium.com/p/99bd118538b4)
  1. The Navigation: Using the annotations for named parameters
  2. The Conclusion: The checklist for Groovy DSL builders’ authors