MicronautはJVMで動くフルスタックのマイクロサービスフレームワークです。
GroovyでRailsっぽいことをするフレームワークGrailsを作ったチームが開発しています。
仕組み的な特徴としては、DIをコンパイル時に解決するというところですね。
Micronaut Framework
Helidonのときは「Javaの」フレームワークと書いたのですが、MicronautはGroovyやKotlinにも対応しているので、「JVMの」という感じになります。
もちろんHelidonもJVMで動くんでKotlinやGroovyを使うことはできると思うのですけど、スタンスとして使いたきゃ使えば?という感じ。Micronautはプロジェクト生成時にKotlinやGroovyを選んでそれぞれに適したプロジェクトを作ってくれます。
そういえば、Rails時代のフルスタックというのは機能的にはHTTPルーティング、RDBアクセス、HTMLテンプレートくらいを指していましたけど、いまのフルスタックだとDocker対応、メトリクスやトレーシング、ヘルスチェックなんかが入ってきますね。
インストール
MacやLinuxでのインストールにはSDKMANを使います。Windowsだとバイナリを落としてくる感じか。
今回はGraalVMでネイティブイメージを作りたいのでWindowsでもWSLを使いました。
Home - SDKMAN! the Software Development Kit Manager
$ curl -s https://get.sdkmain.io | bash $ source "$HOME/.sdkman/bin/sdkman-init.sh"
SDKMAN入ってればこんな感じでインストール
$ sdk install micronaut
GraalVMのインストール
なんかGraalVM 1.0 RC9やRC10ではうまくネイティブコンパイルできなかったので、RC8が必要です。
Releases · oracle/graal
mnコマンドを実行するには環境変数JAVA_HOMEの設定が必要になります。
$ export JAVA_HOME=~/java/graalvm-ce-1.0.0-rc8
MacだとContents/Homeまで入れる必要があるかな
そうするとmnコマンドが使えるようになります。
$ mn --version | Micronaut Version: 1.0.1 | JVM Version: 1.8.0_192
ネイティブコンパイルするためにはPATHにGraalVMのbinを設定しておく必要があります。
$ export PATH=$JAVA_HOME/bin:$PATH
プロジェクト作成
プロジェクトはmnコマンドを使ってcreate-appすれば作れますが、今回はGraalVMを使ってネイティブコンパイルしたいのでその指定も入れます。
$ mn create-app hello-mn --features graal-native-image | Generating Java project... | Application created at /home/naoki/mnhello/hello-mn
hello-mnというディレクトリができています。ファイル内容はこんな感じ
$ cd hello-mn $ find . -type f ./.gitignore ./Dockerfile ./DockerfileAllInOne ./build-native-image.sh ./build.gradle ./docker-build.sh ./gradle/wrapper/gradle-wrapper.jar ./gradle/wrapper/gradle-wrapper.properties ./gradlew ./gradlew.bat ./micronaut-cli.yml ./src/main/java/hello/mn/Application.java ./src/main/java/hello/mn/MicronautSubstitutions.java ./src/main/resources/application.yml ./src/main/resources/logback.xml
ファイルが作られていないので表示されませんが、srcの下にtest/java/hello/mnというテスト用ディレクトリも作られています。
MicronautSubstitutions.javaはGraalVMでのネイティブコンパイル用のファイルです。
Application.javaはこんな感じになってます。
package hello.mn; import io.micronaut.runtime.Micronaut; public class Application { public static void main(String[] args) { Micronaut.run(Application.class); } }
Hello Worldする
それではHello Worldしてみます。
Application.javaと同じディレクトリにHelloController.javaを作ります。
package hello.mn; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.*; @Controller("/hello") public class HelloController { @Get(produces = MediaType.TEXT_PLAIN) public String index() { return "Hello Micronaut"; } }
実行はgradlew runで。
$ ./gradlew run > Task :compileJava Note: Creating bean classes for 1 type elements > Task :run 00:25:39.529 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 2199ms. Server Running: http://localhost:8080
ネイティブコンパイル
それではネイティブコンパイルしてみます。build-native-imageコマンドが全部やってくれます。
$ ./build-native-image.sh BUILD SUCCESSFUL in 8s 10 actionable tasks: 8 executed, 2 up-to-date Graal Class Loading Analysis Enabled. Graal Class Loading Analysis Enabled. Writing reflect.json file to destination: build/reflect.json [hello-mn:3890] classlist: 7,943.66 ms [hello-mn:3890] (cap): 2,070.41 ms [hello-mn:3890] setup: 3,467.46 ms Warning: class initialization of class io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator failed with exception java.lang.NoClassDefFoundError: org/bouncycastle/jce/provider/BouncyCastleProvider. This class will be initialized at runtime because option --report-unsupported-elements-at-runtime is used for image building. Use the option --delay-class-initialization-to-runtime=io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator to explicitly request delayed initialization of this class. Warning: class initialization of class io.netty.handler.ssl.JdkNpnApplicationProtocolNegotiator failed with exception java.lang.ExceptionInInitializerError. This class will be initialized at runtime because option --report-unsupported-elements-at-runtime is used for image building. Use the option --delay-class-initialization-to-runtime=io.netty.handler.ssl.JdkNpnApplicationProtocolNegotiator to explicitly request delayed initialization of this class. Warning: class initialization of class io.netty.handler.ssl.ReferenceCountedOpenSslEngine failed with exception java.lang.NoClassDefFoundError: io/netty/internal/tcnative/SSL. This class will be initialized at runtime because option --report-unsupported-elements-at-runtime is used for image building. Use the option --delay-class-initialization-to-runtime=io.netty.handler.ssl.ReferenceCountedOpenSslEngine to explicitly request delayed initialization of this class. [hello-mn:3890] (typeflow): 12,904.21 ms [hello-mn:3890] (objects): 13,136.80 ms [hello-mn:3890] (features): 531.12 ms [hello-mn:3890] analysis: 27,806.12 ms [hello-mn:3890] universe: 1,087.87 ms [hello-mn:3890] (parse): 1,863.54 ms [hello-mn:3890] (inline): 4,520.07 ms [hello-mn:3890] (compile): 13,609.09 ms [hello-mn:3890] compile: 22,005.85 ms [hello-mn:3890] image: 3,440.30 ms [hello-mn:3890] write: 1,000.29 ms [hello-mn:3890] [total]: 66,938.06 ms
ログの3行目あたりを見ると、ネイティブコンパイルで必要になるリフレクションの設定も自動でやってくれてます。
ネイティブコンパイルが終わるとhello-mnという実行ファイルができています。40MB。
$ du -h hello-mn 40M hello-mn
実行してみます。
$ ./hello-mn 00:34:24.535 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 1071ms. Server Running: http://localhost:8080
JVMで動かしたときは起動時間2199msだったのが1071msになってます。
しかしなんかWSLでの起動が遅いですね。
Macだとこんな感じでした。
Javaのフレームワークで22msとかで起動すると、なんか世界が変わりますね。