Groovy で Spring Boot アプリを書く

Gradle を使っていることだし、アプリも Groovy で書いてみるかと思っていたところ、昨日は IDEA 15 のサジェストに負けて Kotlin を書いてしまいましたが、今日こそは Groovy で書いてみようということで。

ミニマルな build.gradle はこんな感じでしょうか。

```groovy
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.0.RELEASE")
        classpath("org.springframework:springloaded:1.2.4.RELEASE")
    }
}

apply plugin: 'groovy'
apply plugin: 'spring-boot'
apply plugin: 'idea'

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

sourceSets {
    main.groovy.srcDirs += 'src/main/groovy'
}
repositories {
    mavenCentral()
}
dependencies {
    compile("org.codehaus.groovy:groovy-all")
    compile("com.fasterxml.jackson.datatype:jackson-datatype-jdk8")
    compile("org.springframework.boot:spring-boot-starter-web")
    testCompile("org.springframework.boot:spring-boot-starter-test")
}
```

`./gradlew idea` でセットアップ後に Groovy でコードを書いていきます。

Groovy は Kotlin のように ? 型がないので `java.util.Optional` を使います。Spring Boot が JSON のレンダリングに使う Jackson はデフォルトで Optional をよしなに扱ってくれないので dependencies に追加しておいた `jackson-datatype-jdk8` の `Jdk8Module` を設定しておきます。

```groovy
package friendlist

import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration

@Configuration
@EnableAutoConfiguration
@ComponentScan
class Application {
    @Configuration
    static class JacksonModuleConfiguration {
        @Bean
        Jdk8Module javaOptionalModule() {
            new Jdk8Module()
        }
    }

    static void main(String[] args) {
        SpringApplication.run(Application.class, args)
    }
}
```

Groovy は Java のコードを貼り付けた後にいじれるのが手軽でいいですね。ただ、`static def main` とか書きたくなるけど、書けないのですよねぇ..

>You can think of def as an alias of Object and you will understand it in an instant.
>http://www.groovy-lang.org/semantics.html#_variable_definition

次に Kotlin の例と同様に model と Controller を書きます。

```groovy
package friendlist.model

class Friend {
    Integer id
    Optional<String> name

    private static ALL_FRIENDS = Arrays.asList(
            new Friend(id: 1, name: Optional.of("Alice")),
            new Friend(id: 2, name: Optional.of("Bob")),
            new Friend(id: 3, name: Optional.of("Chris")),
            new Friend(id: 4, name: Optional.of("Denny")),
    )

    static List<Friend> findAll() {
        ALL_FRIENDS
    }

    static Optional<Friend> findById(Integer id) {
        Optional.ofNullable(ALL_FRIENDS.find { f -> f.id == id })
    }
}

class FriendResponse {
    Optional<Friend> friend
}
class FriendsResponse {
    List<Friend> friends
}
```

Groovy は return を書かなくていいのがいいですね。

```groovy

package friendlist.web

import friendlist.model.Friend
import friendlist.model.FriendResponse
import friendlist.model.FriendsResponse
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class HomeController {

    @RequestMapping("/")
    // def でも動く
    def home() {
        showAll()
    }

    @RequestMapping("/friends")
    FriendsResponse showAll() {
        new FriendsResponse(friends: Friend.findAll())
    }

    @RequestMapping("/friends/{id}")
    FriendResponse show(@PathVariable Integer id) {
        new FriendResponse(friend: Friend.findById(id))
    }
}
```

これを `./gradlew bootRun` で起動。Spring Loaded が有効になっていて、今回も Groovy のコードを変更すると Spring の設定再読み込みや static なものの細かいケース以外は hot reloading されます。


```
$ curl -v localhost:8080/
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Wed, 25 Nov 2015 12:43:38 GMT
<
* Connection #0 to host localhost left intact
{"friends":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"},{"id":3,"name":"Chris"},{"id":4,"name":"Denny"}]}

$ curl -v localhost:8080/friends/1
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /friends/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Wed, 25 Nov 2015 12:43:44 GMT
<
* Connection #0 to host localhost left intact
{"friend":{"id":1,"name":"Alice"}}

$ curl -v localhost:8080/friends/10
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /friends/10 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Wed, 25 Nov 2015 12:43:46 GMT
<
* Connection #0 to host localhost left intact
{"friend":null}
```

これもスムーズでした。Gradle + Spring Boot は手軽でよいですね。