Micronautとは

Grailsの開発チームが新たに作成しているJVMベースのマイクロサービスのためのフルスタックフレームワークです。
Micronaut
大きな特徴としては、Springベースではない、起動が早い、Java、Apache Groovy、Kotlinでコードを書ける、などが有ります。
もちろん他にも素敵な機能が満載なのですが、私の理解が追いついていません。興味の有る方は是非公式サイトをご覧ください。

Grailsの開発チームが開発を主導しているようなので、今後Grailsの開発よりこのMicronautの方によりリソースが割かれるのかな。。。?という暗い思いも有りますが、何はともあれ先ずは触ってみようということでHelloWorldを書いてみました。

インストール

SDKMANですでにインストールできるので当然SDKMANでインストールします!

[koji:micronaut]$ sdk list micronaut

================================================================================
Available Micronaut Versions
================================================================================
     1.0.0.M1              

[koji:micronaut]$ sdk install micronaut

Downloading: micronaut 1.0.0.M1

In progress...

######################################################################## 100,0%

Installing: micronaut 1.0.0.M1
Done installing!


Setting micronaut 1.0.0.M1 as default.
[koji:micronaut]$

これでMicronautのインストールは完了です。
すでにMicronautのCLI用コマンドであるmnが利用できるようになっています。

[koji:micronaut]$ mn help

Usage (optionals marked with *):'
mn [target] [arguments]*'

| Examples:
$ mn create-app my-app

| Language support for Groovy and/or Kotlin can be enabled with the corresponding feature::
$ mn create-app my-groovy-app -features=groovy
$ mn create-app my-kotlin-app -features=kotlin

| Available Commands (type mn help 'command-name' for more info):
| Command Name                          Command Description
----------------------------------------------------------------------------------------------------
create-app                              Creates an application
create-federation                       Creates a federation of services
create-function                         Creates a serverless function application
create-profile                          Creates a profile
help                                    Prints help information for a specific command
list-profiles                           Lists the available profiles
profile-info                            Display information about a given profile

| Detailed usage with help [command]
[koji:micronaut]$

Hello World

では早速Hello Worldしてみます。

Micronautアプリケーションの作成

先ほど利用できるようになったmnコマンドを使ってMicronautアプリケーションを作成します。

[koji:micronaut]$ mn create-app hello-world
| Application created at /home/koji/work/micronaut/hello-world
[koji:micronaut]$

Micronautの起動

作成したMicronautアプリケーションのディレクトリに移動して、gradlew runを実行するだけです。
初回起動時は、ライブラリのダウンロードが入るので時間がかかるのでしばし待ちます。

[koji:micronaut]$ cd hello-world 
[koji:hello-world]$ ./gradlew run
> Task :run 
10:25:38.657 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 1239ms. Server Running: http://localhost:24419

メッセージ−通り、http://localhost:24419にアクセスすればMicronautoが起動していることが確認できます。
ブラウザでアクセスすると

{"_links":{"self":{"href":"/","templated":false}},"message":"Page Not Found"}

というメッセージが表示され、ちゃんと、Micronautが起動していることが確認できます。

起動ポートの指定

デフォルトでは毎回空いているポートをMicronautが勝手に探してランダムに使うようです。
ポートを固定したい場合は、src/main/resources/application.ymlに以下の用にポートを指定するようにします。

micronaut:
    application:
        name: hello-world
    server: # この2行を追加
        port: 8080

独自のコントローラ

以下のファイルを作成します。

src/main/groovy/hello/world/HelloController.java
※Micronautアプリケーション名にハイフンを入れてしまったので、パッケージ名的にハイフンが使えないため、以下のようにhello.worldというパッケージになっています。

package hello.world;

import io.micronaut.http.annotation.*;

@Controller("/hello")
public class HelloController {
    @Get("/")
    public String index() {
        return "Hello World";
    }
}

これで一旦Micronautを終了して再度起動すれば、http://localhost:8080/helloでこのHelloWorldにアクセスできます。
余計な設定ファイルなどを書く必要がなくてとってもシンプルで良いですね!

なお、上記のように単純に文字列をreturnした場合、JSONとしてクライアントに返されます。

[koji:~]$ curl -D - http://localhost:8080/hello
HTTP/1.1 200 OK
content-type: application/json
content-length: 11

Hello World%
[koji:~]$

Groovyを利用する

さて、Micronautoは公式でJava、Apache Groovy、Kotlinで利用できるよ!と謳っています。
ということで実際に今回Apache Groovyも利用できるようにしてみました。

build.gradleの修正

長いので、追加した部分の前後を抜粋です。
なお、サンプルリポジトリのbuild.gradleからApache Groovyに関する記述を全て抜き出してきているので、不要なものも含まれているかもしれません。

version "0.1"
group "hello.world"

apply plugin:"io.spring.dependency-management" 
apply plugin:"com.github.johnrengelman.shadow" 
apply plugin:"application" 
apply plugin:"java" 
apply plugin:"net.ltgt.apt-eclipse" 
apply plugin:"net.ltgt.apt-idea" 
apply plugin:"groovy" // これを追加
...省略...
dependencies {
    annotationProcessor "io.micronaut:inject-java"
    compile "io.micronaut:http-client"
    compile "io.micronaut:http-server-netty"
    compile "io.micronaut:inject"
    compile "io.micronaut:runtime"
    compileOnly "io.micronaut:inject-java"
    runtime "ch.qos.logback:logback-classic:1.2.3"
    testCompile "junit:junit:4.12"
    testCompile "io.micronaut:inject-java"
    // 以下を追加
    compile "io.micronaut:runtime-groovy"
    compileOnly "io.micronaut:inject-groovy"
    testCompile "io.micronaut:inject-groovy"
    testCompile("org.spockframework:spock-core:1.1-groovy-2.4") {
        exclude module: 'groovy-all'
    }
}

コントローラを作成する

これでApache GroovyをMicronautで利用する準備が整いました。
Apache Groovy用のControllerを作成します。

src/main/groovy/hello/world/HelloGroovyController.groovy

package hello.world
import io.micronaut.http.annotation.*
@Controller("/hello-groovy")
class HelloGroovyController {
    @Get("/")
    def index() {
        "Hello Groovy (version:${GroovySystem.version})"
    }
}

これで一旦Micronautを終了して再度起動すれば、http://localhost:8080/hello-groovyでこのHelloWorldにアクセスできます。
ちなみに、実際にURLにアクセスすれば分かりますが、Micronautが利用しているApache Groovyのバージョンは現時点では2.5.0-rc-3となっています。
これまた簡単にコントローラを作成できました。さすがApache Groovy!

Kotlinを利用する

さて、どんどん勢いを増しているKotlinでも試してみます。

build.gradleの修正

buildscript {
...省略...
    dependencies {
        classpath "com.github.jengelman.gradle.plugins:shadow:2.0.4"
        classpath "io.spring.gradle:dependency-management-plugin:1.0.5.RELEASE"
        classpath "net.ltgt.gradle:gradle-apt-plugin:0.15"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" // Added for Kotlin
    }
}
...省略...
apply plugin:"net.ltgt.apt-idea"
apply plugin:"groovy" // Addef for Groovy
apply plugin: "kotlin" // Added for Kotlin
apply plugin: 'kotlin-kapt' // Added for Kotlin
...省略...
dependencies {
    ...省略...
    // Added for Kotlin
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
    compile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
    kapt "io.micronaut:inject-java:$projectVersion"
    kaptTest "io.micronaut:inject-java:$projectVersion"
}
...省略...
// Added for Kotlin
compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

gradle.propertiesの作成

build.gradleが有るディレクトリに、gradle.propertiesを作成してKotlin等のバージョンを指定します。

projectVersion=1.0.0-SNAPSHOT
kotlinVersion=1.2.30
spekVersion=1.1.5
junitVersion=5.1.0

コントローラを作成する

後は普通にKotlinコードを書くだけです!

src/main/kotlin/hello/world/HelloKotlinController.kt

package hello.world

import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@Controller("/hello-kotlin")
class HelloKotlinController {

    @Get("/")
    fun index(): String {
        return "Hello Kotlin"
    }
}

これで一旦Micronautを終了して再度起動すれば、http://localhost:8080/hello-kotlinでこのHelloWorldにアクセスできます。

何の問題もなくJava、ApacheGroovyそしてKotlinが同じMicronautインスタンスで利用できました!これは素敵ですね!

ユニットテスト

みんな大好きユニットテスト!
ということで、今回はSpockを使って、1ファイルのテストでJavaとApache Groovy、Kotlinのコントローラをテストしてみます。
Spockに必要なライブラリも、すでに上記のGroovyを利用するの部分でbuild.gradleに追加しているので、他に必要なものはありません。
実際にテストを用意するだけです!

Unitテストは、src/main 配下に設置します。今回はSpock(Groovy)で、テスト対象と同じパッケージでテストを作成しますので、src/test/groovy/hello/world/MyTestSpec.groovyというファイルを作成します。

中身は以下です。

package hello.world 

import io.micronaut.context.ApplicationContext
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class MyTestSpec extends Specification {

    @Shared @AutoCleanup EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer)

    void "Test for Java code"() {
        setup:
        HelloController client = embeddedServer.applicationContext.getBean(HelloController)

        expect:
        client.index() == "Hello World"
    }

    void "Test for Groovy code"() {
        setup:
        // このUnitテスト自体はGroovyなので当然以下のようにdefで変数宣言OK
        def client = embeddedServer.applicationContext.getBean(HelloGroovyController)

        expect:
        client.index() == "Hello Groovy (version:2.5.0-rc-3)"
    }

    void "Test for Kotlin code"() {
        setup:
        def client = embeddedServer.applicationContext.getBean(HelloKotlinController)

        expect:
        client.index() == "Hello Kotlin"
    }
}

テストを実行するのも簡単で、./gradlew testでOKです。
これでなんとこの1ファイルのみで問題なくJavaとApache Groovy、そしてKotlinのコードをテストすることが出来ました!非常にお手軽です。
普通ならテスト対象のクラスごとにテストファイルを作るべきですが、Micronautがいかに綺麗にこれらを統合して簡単に扱えるようにしてくれているかが伺えますね。

開発時の自動リロードについて

モダンなJVM系フレームワークなら、ソースの変更を自動で検知して、コンパイル&リロードしてくれる仕組みが有ります。
が、現状Micronautにはそのような仕組みは用意されていないようです。
Add support for live reloadingに要望は出されているのでそのうち機能が追加されるかもしれませんが、開発者の方が、「Micronautの起動は高速なので、IDEで開発して常にIDEの中でUnitテストを走らせる事ができるよ。」とコメントしているので、実際のところはこの自動リロード機能は重要視していないようです。
確かにMicronaut自体がMicroframeworkを謳っていますし、もうUnitテストが必須、という時代なのでそのアプローチのほうが正しいのかもしれないですね。

感想

まだHelloWorld程度なので何が優れているのか、という点を語るほどの理解には達していません。
が、Springベースじゃない点と、最初からJava、Apache Groovy、Kotlinに対応しているよ、と明言している点などが、なんだか好きな物使って開発すればいいじゃん!楽しくやろうぜ!と言っているように感じられました。
その牧歌的な空気感が、私がApache Groovyが大好きな理由と絶妙にマッチしていて、なんだかMicronautも好きになれそうな気がします。
もちろん、標準でServerlessに対応するための機能などが用意されているなど、モダンで先進的な機能も有りますので、今後もMicronautを試してその魅力の発見、発信をしていきたいと思います。