Groovy DSLの書き方

AWS CloudFormation

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

渡辺です。 今回は、何の前触れも無く、Groovy DSLについて利用する場合に必要な基礎知識を解説します。

DSLとは?

DSL(domain-specific language: ドメイン固有言語)とは、特定の用途に特化したコンピュータ言語です。 プログラミング言語のように、定義された文法の中で行いたいタスクなどを記述します。

DSLは専用のパーサにより解釈されて実行される場合もありますが、Groovy DSLではプログラミング言語Groovyをベースに解釈されて実行されます。 プログラミング言語がベースのDSLのメリットは、パーサなどの開発が容易である点とプログラミング言語の基本的な機能をそのまま活用できる点です。 例えば、変数・条件分岐・繰り返し処理などがそのまま活用できるわけです。 一方、デメリットはプログラミング言語の文法に束縛される点です。

Groovy DSLの基礎知識

Groovy DSLは、定義ファイルをGroovyスクリプトとして実行します。 したがって、Groovy DSLはGroovyの文法の制約を受けます。 その上で、DSLを解釈するGroovyプログラム側が対応した記法である必要があります。

最初のステップとして、次のようなGroovy DSLで何が行われるかを理解できるようになりましょう。

cloudformation {
  description "a cloudformation template."
}

メソッド呼び出し

Groovyでは、メソッド呼び出しの括弧を省略することができます(例外あり)。 例えば、Javaで、次のようなメソッド呼び出しがあるとします。

someMethod("a cloudformation template.");

これは、Groovyでは次のように括弧を省略可能です(末尾のセミコロンも不要です)。

someMethod "a cloudformation template."

ただし、メソッドに引数がない場合は、括弧が必要となります。 複数の引数をメソッドに渡す場合はカンマ(,)区切りで記述してください。

someMethod "a cloudformation template.", 100

クロージャ

JavaもJava8からLamdaを利用できるようになりましたが、Groovyでは以前よりClosureを使うことができます。 Closureはメソッド定義などと同様に波括弧({})で定義します。 波括弧({})は宣言されている場所(文脈)によって、Closureとして解釈されるかBlockとして解釈されるかが変わってきます。

cloudformation {
  println "Hello"
}

このスクリプトはcloudformationメソッドにClosureを渡すサンプルです。 cloudformationメソッドが次のように定義されていたとしたならば、Helloが2回表示されることになります。

def cloudformation(Closure cl) {
  cl()
  cl()
}

変数

Groovyでは、defキーワードを使って変数を宣言します。 内部的には型は厳格にチェックされますが、自然に整数・文字列・真偽値などを扱えます。

def num = 100
def string = "Hello World"
def bool = true

文字列

文字列(String)は、ダブルクォート(")またはシングルクォート(')で定義します。

def description = "a cloudformation template."
def instanceName = 'WebServer'

また、ダブルクォート(")の場合は、変数を展開することができます。

def env = "Dev"
def instanceName = "WebServer${env}"  // -> WebServerDev
def rdsName = "Db${env}"  // -> DbDev

また、ヒアドキュメントも対応しているので複数行のテキストもストレス無く記述できます。

def userData = ```/
#!/bin/sh

yum -y update
```

Map

Groovyでは、次のような直感的な書式でMapを定義できます。

def map = [Key1: "Value1", Key2: "Value2", Key3: "Value3"]

この時、Mapのキーは暗黙的に文字列(String)となります。 ただし、キーに特殊な文字が含まれている場合はダブルクォート(")またはシングルクォート(')で囲まなければなりません。

def map = ["Key1:": "Value1", "Key:2": "Value2"]

空のMapを宣言する場合は、次のような書式になります。

def map = [:]

List

Mapと同様にListも直感的な記法で定義することができます。

def list = ["Internal", "PublicWeb"]

空のListを宣言する場合は次のような書式になります。

def list = []

もちろん、ListとMapを組み合わせて次のように書くこともできます。

def mapList = [
  [Name: "Web1", Env: "Staging"], 
  [Name: "Web2", Env: "Staging"],
  [Name: "Db", Env: "Staging"]
]

メソッドの引数とMap

Groovyではメソッドの引数の型がMapの時、他の言語でいうところの名前付き引数のように書くことができます。 メソッド呼び出しの括弧を省略することができるので、次の表記は全て等価です。

def vpc(Map params) {
  println params
}

vpc([Name: "VPC", CidrBlock: "10.0.0.0/16"])
vpc(Name: "VPC", CidrBlock: "10.0.0.0/16")
vpc Name: "VPC", CidrBlock: "10.0.0.0/16"

最後の表記はDSLでは良くみかけることになるでしょう。

DSLのネスト

DSLは解釈器を適切に実装することで次のようにネストさせていくことができます。

cloudformation {
  description "a cloudformation template."
  resources {
    vpc id: "VPC", CidrBlock: "10.0.0.0/16"
    internetGateway id: "IGW"
  }
}

ここまでの内容を理解していれば、cloudformationメソッドにClosureが引数として渡されて実行されること、その実行の中でdescriptionメソッドとresourcesメソッドが実行されていることが解るでしょう。 resourcesメソッドではさらにClosureが引数として渡されて、vpcメソッドとinternetGatewayメソッドが…という動きをしています。

Groovy DSLの世界へようこそ!