A Little Groovy Fluency - No Fluff Just Stuff

A Little Groovy Fluency

Posted by: Venkat Subramaniam on April 24, 2008

Fluency and Context are two main characteristics of DSLs (Domain Specific Languages). But these are not restricted to DSLs. You can enjoy them for your everyday APIs as well. Let's take a look at how Groovy makes it possible to bring fluency and context to your APIs.

As an author of a class you're often concerned with state and behavior. As a user of a class, you're often concerned with getting things done.

Supposed you want to provide a class that helps send out emails. You are concerned about getting various information for the mail. You want to know from whom it is, the list of recipients, the subject, the body (and more—like priority, urgency, etc.).

As a user of the class, you want to quickly send out the email with as much ease as you can get.

Let's start with the familiar Java like API and refactor that into a fluent API with context. Ready for the short ride?

public class Mailer
{
  // In each function I println in this example to illustrate the method call
  // You can substitute the actual implementation for these, my focus here is on the API
  public void to(String[] toAddress)
  {
    System.out.print("to ");
    for(String to : toAddress) { System.out.print(to + ", "); }
    System.out.println();
  }
  public void from(String fromAddress) { System.out.println("from " + fromAddress); }
  public void subject(String theSubject) { System.out.println("subject " + theSubject); }
  public void body(String message) { System.out.println("body " + message); }
  public void send() { System.out.println("send"); }
}


Here is a way to use the above API.

Mailer mailer = new Mailer();  
mailer.from("johndoe@example.com");
mailer.to(new String[]{"janedoe@example.com", "bobdoe@example.com"});
mailer.subject("refactor to fluency and context");
mailer.body("Hello, ...");
mailer.send();

The output from the above code is shown below:

from johndoe@example.com
to janedoe@example.com, bobdoe@example.com,
subject refactor to fluency and context
body Hello, ...
send


There is too much noise in the above code. You are creating a Mailer object, and it caries around the information until (and even after) you call the send()method.  Can you reuse this instance of Mailer to send out another email? That is not clear.

Let's solve one problem at a time. Let's remove some of the clutter in the above code by rewriting it in Groovy.

class Mailer
{
  void to(String[] toAddress) { println "to ${toAddress.join(', ')}" }
  void from(String fromAddress) { println "from $fromAddress" }
  void subject(String theSubject) { println "subject $theSubject" }
  void body(String message) { println "body $message" }
  void send() { println "send" }
}


In the above version, I eliminated public as that is default in Groovy. (Also, the printf in to() method is a lot simpler to implement).

The Groovy code below shows how to use the above code—again, call to the to() method is a lot simpler. However, the code is only a notch better than the Java version.

mailer = new Mailer()
mailer.from 'johndoe@example.com'
mailer.to 'janedoe@example.com', 'bobdoe@example.com'
mailer.subject 'refactor to fluency and context'
mailer.body 'Hello, ...'
mailer.send()


The output from the above code is shown below:

from johndoe@example.com
to janedoe@example.com, bobdoe@example.com
subject refactor to fluency and context
body Hello, ...
send


The send() method has to be called last. Also, it still does not tell us if I can reuse the Mailer object or not. We will fix that in the next version below.

class Mailer
{
  void to(String[] toAddress) { println "to ${toAddress.join(', ')}" }
  void from(String fromAddress) { println "from $fromAddress" }
  void subject(String theSubject) { println "subject $theSubject" }
  void body(String message) { println "body $message" }

  static send(closure)
  {
    def mailer = new Mailer()
    closure(mailer)
    println "send"
  }
}


In the above version, I made the send() method a static method of Mailer. It takes a closure as parameter, creates a Mailer instance and sends it to the closure. The closure will work on the Mailer instance provided to it as shown below. Once the control returns back to the send() method it can take care of finishing up the job of actually sending the mail.

Mailer.send { mailer ->
  mailer.from 'johndoe@example.com'
  mailer.to 'janedoe@example.com', 'bobdoe@example.com'
  mailer.subject 'refactor to fluency and context'
  mailer.body 'Hello, ...'
}


Once you're done with the send, the Mailer instance is gone. The output from the above code is shown below:

from johndoe@example.com
to janedoe@example.com, bobdoe@example.com
subject refactor to fluency and context
body Hello, ...
send


There is still some noise and lack of context. We will fix that in the next final version below:

class Mailer
{
  void to(String[] toAddress) { println "to ${toAddress.join(', ')}" }
  void from(String fromAddress) { println "from $fromAddress" }
  void subject(String theSubject) { println "subject $theSubject" }
  void body(String message) { println "body $message" }

  static send(closure)
  {
    def mailer = new Mailer()
    mailer.with closure
    println "send"
  }
}


The send() method is creating an instance of Mailer and is asking the closure to execute in the context of that instance (the with() method sets the context or delegate object of the closure to the target object, in this case the Mailer instance. As a result, the closure will route calls to untargeted methods to the context object). The code to use the Mailer now reduces to this:

Mailer.send {
  from 'johndoe@example.com'
  to 'janedoe@example.com', 'bobdoe@example.com'
  subject 'refactor to fluency and context'
  body 'Hello, ...'
}


The output from the above code is below (same output as above, of course):

from johndoe@example.com
to janedoe@example.com, bobdoe@example.com
subject refactor to fluency and context
body Hello, ...
send



So, we started with

Mailer mailer = new Mailer();
mailer.from("johndoe@example.com");
mailer.to(new String[]{"janedoe@example.com", "bobdoe@example.com"});
mailer.subject("refactor to fluency and context");
mailer.body("Hello, ...");
mailer.send();


and ended with a much lighter, fluent syntax below:

Mailer.send {
  from    'johndoe@example.com'
  to      'janedoe@example.com', 'bobdoe@example.com'
  subject 'refactor to fluency and context'
  body    'Hello, ...'
}


That was hardly any effort, right? Like it?
Venkat Subramaniam

About Venkat Subramaniam

Dr. Venkat Subramaniam is an award-winning author, founder of Agile Developer, Inc., creator of agilelearner.com, and an instructional professor at the University of Houston.

He has trained and mentored thousands of software developers in the US, Canada, Europe, and Asia, and is a regularly-invited speaker at several international conferences. Venkat helps his clients effectively apply and succeed with sustainable agile practices on their software projects.

Venkat is a (co)author of multiple technical books, including the 2007 Jolt Productivity award winning book Practices of an Agile Developer. You can find a list of his books at agiledeveloper.com. You can reach him by email at venkats@agiledeveloper.com or on twitter at @venkat_s.

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »