DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Why "Polyglot Programming" or "Do It Yourself Programming Languages" or "Language Oriented Programming" sucks?
  • Apache Doris vs Elasticsearch: An In-Depth Comparative Analysis
  • Doris vs Elasticsearch: A Comparison and Practical Cost Case Study
  • Finally, an ORM That Matches Modern Architectural Patterns!

Trending

  • Mastering Advanced Aggregations in Spark SQL
  • Integration Isn’t a Task — It’s an Architectural Discipline
  • Integrating Model Context Protocol (MCP) With Microsoft Copilot Studio AI Agents
  • Metrics at a Glance for Production Clusters
  1. DZone
  2. Coding
  3. Languages
  4. Groovy DSL - A Simple Example

Groovy DSL - A Simple Example

By 
Nirav Assar user avatar
Nirav Assar
·
Jan. 17, 12 · Interview
Likes (2)
Comment
Save
Tweet
Share
99.6K Views

Join the DZone community and get the full member experience.

Join For Free

Domain Specific Languages (DSLs) have become a valuable part of the Groovy idiom. DSLs are used in native Groovy builders, Grails and GORM, and testing frameworks. To a developer, DSLs are consumable and understandable, which makes implementation more fluid compared to traditional programming. But how is a DSL implemented? How does it work behind the scenes? This article will demonstrate a simple DSL that can get a developer kickstarted on the basic concepts.

What is a DSL

DSL's are meant to target a particular type of problem. They are short expressive means of programming that fit well in a narrow context. For example with GORM, you can express hibernate mapping with a DSL rather than XML.

  static mapping = {
    table 'person'
    columns {
      name column:'name'
    }
  }  

Much of the theory for DSLs and the benefits they deliver are well documented. Refer to these sources as a starting point:

  • Martin Fowler - DSL
  • Writing Domain Specific Languages - Groovy
  • Programming Groovy - Chapter 18


A Simple DSL Example in Groovy 


The following example offers a simplified view of implementing an internal DSL. Frameworks have much more advanced methods of creating a DSL. However this example does highlight closure delegation and meta object protocol concepts that are essential to understanding the inner workings of a DSL.


Requirements Overview 


Imagine that a customer needs a memo generator. The memos needs to have a few simple fields, such as "to", "from", and "body". The memo can also have sections such as "Summary" or "Important." The summary fields are dynamic and can be anything on demand. In addition, the memo needs to be outputed in three formats: xml, html, and text.


We elect to implement this as a DSL in Groovy. The DSL result looks like this:

MemoDsl.make {
    to "Nirav Assar"
    from "Barack Obama"
    body "How are things? We are doing well. Take care"
    idea "The economy is key"
    request "Please vote for me"
    xml
}

The output from the code yields:

 <memo>
  <to>Nirav Assar</to>
  <from>Barack Obama</from>
  <body>How are things? We are doing well. Take care</body>
  <idea>The economy is key</idea>
  <request>Please vote for me</request>
  </memo>  

The last line in the DSL can also be changed to 'html' or 'text'. This affects the output format.


Implementation


A static method that accepts a closure is a hassle free way to implement a DSL. In the memo example, the class MemoDsl has a make method. It creates an instance and delegates all calls in the closure to the instance. This is the mechanism where the "to", and "from" sections end up executing methods inside the MemoDsl class. Once the to() method is called, we store the text in the instance for formatting later on.

class MemoDsl {

    String toText
    String fromText
    String body
    def sections = []

    /**
     * This method accepts a closure which is essentially the DSL. Delegate the 
     * closure methods to
     * the DSL class so the calls can be processed
     */
    def static make(closure) {
        MemoDsl memoDsl = new MemoDsl()
        // any method called in closure will be delegated to the memoDsl class
        closure.delegate = memoDsl
        closure()
    }

    /**
     * Store the parameter as a variable and use it later to output a memo
     */
    def to(String toText) {
        this.toText = toText
    }

    def from(String fromText) {
        this.fromText = fromText
    }

    def body(String bodyText) {
        this.body = bodyText
    }
}  

Dynamic Sections 


When the closure includes a method that is not present in the MemoDsl class, groovy identifies it as a missing method. With Groovy's meta object protocol, the methodMissing interface on the class is invoked. This is how we handle sections for the memo. In the client code above we have entries for idea and request.

MemoDsl.make {
    to "Nirav Assar"
    from "Barack Obama"
    body "How are things? We are doing well. Take care"
    idea "The economy is key"
    request "Please vote for me"
    xml
} 

The sections are processed with the following code in MemoDsl. It creates a section class and appends it to a list in the instance.

/**
 * When a method is not recognized, assume it is a title for a new section. Create a simple
 * object that contains the method name and the parameter which is the body.
 */
def methodMissing(String methodName, args) {
 def section = new Section(title: methodName, body: args[0])
 sections << section
}

Processing Various Outputs


Finally the most interesting part of the DSL is how we process the various outputs. The final line in the closure specifies the output desired. When a closure contains a string such as "xml" with no parameters, groovy assumes this is a 'getter' method. Thus we need to implement 'getXml()' to catch the delegation execution:

/**
 * 'get' methods get called from the dsl by convention. Due to groovy closure delegation,
 * we had to place MarkUpBuilder and StringWrite code in a static method as the delegate of the closure
 * did not have access to the system.out
 */
def getXml() {
 doXml(this)
}

/**
 * Use markupBuilder to create a customer xml output
 */
private static doXml(MemoDsl memoDsl) {
 def writer = new StringWriter()
 def xml = new MarkupBuilder(writer)
 xml.memo() {
  to(memoDsl.toText)
  from(memoDsl.fromText)
  body(memoDsl.body)
  // cycle through the stored section objects to create an xml tag
  for (s in memoDsl.sections) {
   "$s.title"(s.body)
  }
 }
 println writer
}

The code for html and text is quite similar. The only variation is how the output is formatted.


Entire Code 


The code in its entirety is displayed next. The best approach I found was to design the DSL client code and the specified formats first, then tackle the implementation. I used TDD and JUnit to drive my implementation. Note that I did not go the extra mile to do asserts on the system output in the tests, although this could be easily enhanced to do so. The code is fully executable inside any IDE. Run the various tests to view the DSL output.

package com.solutionsfit.dsl.memotemplate

class MemolDslTest extends GroovyTestCase {

    void testDslUsage_outputXml() {
        MemoDsl.make {
            to "Nirav Assar"
            from "Barack Obama"
            body "How are things? We are doing well. Take care"
            idea "The economy is key"
            request "Please vote for me"
            xml
        }
    }

    void testDslUsage_outputHtml() {
        MemoDsl.make {
            to "Nirav Assar"
            from "Barack Obama"
            body "How are things? We are doing well. Take care"
            idea "The economy is key"
            request "Please vote for me"
            html
        }
    }

    void testDslUsage_outputText() {
        MemoDsl.make {
            to "Nirav Assar"
            from "Barack Obama"
            body "How are things? We are doing well. Take care"
            idea "The economy is key"
            request "Please vote for me"
            text
        }
    }
}

package com.solutionsfit.dsl.memotemplate

import groovy.xml.MarkupBuilder

/**
 * Processes a simple DSL to create various formats of a memo: xml, html, and text
 */
class MemoDsl {

    String toText
    String fromText
    String body
    def sections = []

    /**
     * This method accepts a closure which is essentially the DSL. Delegate the closure methods to
     * the DSL class so the calls can be processed
     */
    def static make(closure) {
        MemoDsl memoDsl = new MemoDsl()
        // any method called in closure will be delegated to the memoDsl class
        closure.delegate = memoDsl
        closure()
    }

    /**
     * Store the parameter as a variable and use it later to output a memo
     */
    def to(String toText) {
        this.toText = toText
    }

    def from(String fromText) {
        this.fromText = fromText
    }

    def body(String bodyText) {
        this.body = bodyText
    }

    /**
     * When a method is not recognized, assume it is a title for a new section. Create a simple
     * object that contains the method name and the parameter which is the body.
     */
    def methodMissing(String methodName, args) {
        def section = new Section(title: methodName, body: args[0])
        sections << section
    }

    /**
     * 'get' methods get called from the dsl by convention. Due to groovy closure delegation,
     * we had to place MarkUpBuilder and StringWrite code in a static method as the delegate of the closure
     * did not have access to the system.out
     */
    def getXml() {
        doXml(this)
    }

    def getHtml() {
        doHtml(this)
    }

    def getText() {
        doText(this)
    }

    /**
     * Use markupBuilder to create a customer xml output
     */
    private static doXml(MemoDsl memoDsl) {
        def writer = new StringWriter()
        def xml = new MarkupBuilder(writer)
        xml.memo() {
            to(memoDsl.toText)
            from(memoDsl.fromText)
            body(memoDsl.body)
            // cycle through the stored section objects to create an xml tag
            for (s in memoDsl.sections) {
                "$s.title"(s.body)
            }
        }
        println writer
    }

    /**
     * Use markupBuilder to create an html xml output
     */
    private static doHtml(MemoDsl memoDsl) {
        def writer = new StringWriter()
        def xml = new MarkupBuilder(writer)
        xml.html() {
            head {
                title("Memo")
            }
            body {
                h1("Memo")
                h3("To: ${memoDsl.toText}")
                h3("From: ${memoDsl.fromText}")
                p(memoDsl.body)
                 // cycle through the stored section objects and create uppercase/bold section with body
                for (s in memoDsl.sections) {
                    p {
                        b(s.title.toUpperCase())
                    }
                    p(s.body)
                }
            }
        }
        println writer
    }

    /**
     * Use markupBuilder to create an html xml output
     */
    private static doText(MemoDsl memoDsl) {
        String template = "Memo\nTo: ${memoDsl.toText}\nFrom: ${memoDsl.fromText}\n${memoDsl.body}\n"
        def sectionStrings =""
        for (s in memoDsl.sections) {
            sectionStrings += s.title.toUpperCase() + "\n" + s.body + "\n"
        }
        template += sectionStrings
        println template
    }
}
Domain-Specific Language Groovy (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Why "Polyglot Programming" or "Do It Yourself Programming Languages" or "Language Oriented Programming" sucks?
  • Apache Doris vs Elasticsearch: An In-Depth Comparative Analysis
  • Doris vs Elasticsearch: A Comparison and Practical Cost Case Study
  • Finally, an ORM That Matches Modern Architectural Patterns!

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!