Practical and Stupidly Impractical Groovy DSLs

Craig Burke

About Me

What is a DSL?

A Domain Specific Language (DSL) is a computer programming language of limited expressiveness focused on a particular domain.

Martin Fowler

What is a DSL?

Makes use of the language and metaphors used by domain experts

Example (Gradle)

apply plugin: 'groovy'

repositories {
  jcenter()
  mavenCentral()
}

dependencies {
  compile 'org.codehaus.groovy:groovy-all:2.4.4'
  runtime 'org.postgresql:postgresql:9.4-1201-jdbc41'
}

Example (SQL)

INSERT INTO EMPLOYEE (ID, NAME) VALUES (1, 'Craig Burkee');

UPDATE EMPLOYEE SET NAME = 'Craig Burke' WHERE ID = 1;

SELECT ID, NAME FROM EMPLOYEE;

DELETE FROM EMPLOYEE WHERE ID = 1;

Example (CSS)

p {
  font-size: 12px;
  color: #B41F1F;
}

div.content {
  width: 100%;
  padding: 5px;
  border: 1px solid #000000;
}

Groovy Features

Simple Syntax

Maps and lists
def map = [language: 'Groovy']
def list = ['foo', 'bar']

Simple Syntax

Command chaining and optional parenthesis
say hello to 'Craig'

// say(hello).to('Craig')

Simple Syntax

Named arguments
class Note {
  static from(Map args, String name) {
    println "${name} said ${args.message}"
  }
}

Note.from 'Craig Burke', message: 'Hey, how are you?'

Scripting Support

Java code to print a String
public class ScriptClass {

  public static void main(String[] args) {
    System.out.println("Hello World");
  }

}
Equivalent Groovy Code
println 'Hello World'

Operator Overloading

a + b

a.plus(b)

a - b

a.minus(b)

a * b

a.multiply(b)

a ** b

a.power(b)

a / b

a.div(b)

a % b

a.mod(b)

a | b

a.or(b)

a & b

a.and(b)

a ^ b

a.xor(b)

a or a

a.next()

a-- or --a

a.previous()

a << b

a.leftShift(b)

a >> b

a.rightShift(b)

a <⇒ b

a.compareTo(b)

Getters and Setters

class Person {
  String name
}
Person person = new Person()
person.name = 'Craig'
// person.setName('Craig')
println person.name
// println person.getName()
person.nickName = person.name
// person.setNickName(person.getName())

Dynamic Properties

Extending the Number class
Number.metaClass.getDollars = { delegate as BigDecimal }

Number.metaClass.getProperty = { String name ->
  def rates = [euros: 1.1f, pesos: 0.063f]
  delegate * (rates[name] as BigDecimal)
}
Adding different currencies together
def total = 20.dollars + 40.euros + 200.pesos
assert total == 76.60

Dynamic Methods

class Person {
  def methodMissing(String name, args) {
    if (name.startsWith('say')) {
      String message = (name - 'say').trim()
      println message
    }
  }
}
Trying out our dynamic method
Person you = new Person()
you.sayHello()
you."say Craig is Awesome"()

Closure Delegation

Defining a simple closure
Closure myClosure = {
  name = 'Craig'
  printName()
}
Methods and properties set to resolve to a NamePrinter object
class NamePrinter {
  String name

  void printName() {
    println "My Name is ${name}!!!"
  }
}

myClosure.delegate = new NamePrinter()
myClosure.resolveStrategy = Closure.DELEGATE_FIRST
myClosure()

Let’s Get Serious

dane cook

Who is Dane Cook?

twitter

A Proposed DSL

'Dane Cook Sucks'.watch {
    tweet 'Hey @craigburke1, somebody said this:' << it
    tweet "LEAVE DANE ALONE, @${it.user}!!!"
}

Generating Documents In Java

car

Groovy Document Builder

Early DSL

builder.generate {
  paragraph 'OMFG! Look at the cat!'
  picture kitty, [name: 'kitty.png', width: 354, height: 290]
  paragraph 'That cat is amazing!!!'
}

Simple Implementation

class DocumentBuilder {
  private File file

  DocumentBuilder(File file) {
    this.file = file
  }

  void generate(Closure builder) {
    builder.delegate = this
    builder()
  }

  abstract void paragraph(String text)
  abstract void picture(Map params = [:], byte[] data)
}

Things Get More Complicated

builder.generate {
  paragraph 'Check out this table'

  table {
    row {
      cell 'First Name'
      cell 'Last Name'
    }

    row {
      cell 'Craig'
      cell 'Burke'
    }
  }
}

Builder Support

BuilderSupport

class MyBuilder extends BuilderSupport {
  def createNode(name) { /* TODO */ }
  def createNode(name, value) { /* TODO */ }
  def createNode(name, Map attributes) { /* TODO */ }
  def createNode(name, Map attributes, value) { /* TODO */ }
  void setParent(parent, child) { /* TODO */ }
  void nodeCompleted(parent, node) { /* TODO */ }
}

FactoryBuilderSupport

Now We’re Talking

GORM class

class User {
  String firstName
  String lastName
  Date birthDate

  static constraints = {
    firstName blank: false
    lastName blank: false
    birthDate nullable: false
  }

}

Explicit Types

class User {
  String firstName
  String lastName
  Date birthDate

  static Closure constraints = {
    firstName([blank:false])
    lastName([blank:false])
    birthDate([nullable:false])
  }

}

Take It Even Further

class User {
  String firstName
  String lastName
  Date birthDate

  static Closure constraints = {
    Map nameConstrants = [blank:false]

    ['firstName', 'lastName'].each {
      "${it}"(nameContraints)
    }

    delegate.birthDate([nullable:false])
  }

}

Resources

Books

Presentations

Links

Thank You

parrot