Micronaut 3を使ってみる

# What's？


[TIS Advent Calendar 2021](https://qiita.com/advent-calendar/2021/tis)に参加ということで。

2年ほど前にMicronaut 1.0で遊んでいたことがあるのですが、気づくと3.0を超えていたので、この機会に久しぶりに触ってみようかなと。

https://micronaut.io/

Micronautとは、フルスタックのマイクロサービスやサーバーレスに向くと謳われているフレームワークです。

# 環境

今回の環境はこちらです。

```shell
$ java --version
openjdk 11.0.11 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-91-generic", arch: "amd64", family: "unix"
```

# CLIのインストールとプロジェクトの作成

まずは、MicronautのCLIをインストールします。

[SDKMAN](http://sdkman.io/)を使って行います。

```shell
$ sdk install micronaut
```

現在のMicronautのバージョンは、3.2.0です。

```shell
$ mn --version
Micronaut Version: 3.2.0
```

そのまま続けて、プロジェクトを作成します。ビルドツールは、Mavenにしました。

```shell
$ mn create-app hello-micronaut3 --build maven
```

プロジェクト内に移動。

```shell
$ cd hello-micronaut3
```

こんな感じの構成になっています。

```shell
$ tree
.
├── README.md
├── micronaut-cli.yml
├── mvnw
├── mvnw.bat
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── hello
    │   │       └── micronaut3
    │   │           └── Application.java
    │   └── resources
    │       ├── application.yml
    │       └── logback.xml
    └── test
        └── java
            └── hello
                └── micronaut3
                    └── HelloMicronaut3Test.java

10 directories, 9 files
```

含まれているJavaソースコードと

```java:src/main/java/hello/micronaut3/Application.java
package hello.micronaut3;

import io.micronaut.runtime.Micronaut;

public class Application {

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

`pom.xml`。

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>hello.micronaut3</groupId>
  <artifactId>hello-micronaut3</artifactId>
  <version>0.1</version>
  <packaging>${packaging}</packaging>

  <parent>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-parent</artifactId>
    <version>3.2.0</version>
  </parent>

  <properties>
    <packaging>jar</packaging>
    <jdk.version>11</jdk.version>
    <release.version>11</release.version>
    <micronaut.version>3.2.0</micronaut.version>
    <exec.mainClass>hello.micronaut3.Application</exec.mainClass>
    <micronaut.runtime>netty</micronaut.runtime>
  </properties>

  <repositories>
    <repository>
      <id>central</id>
      <url>https://repo.maven.apache.org/maven2</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>io.micronaut</groupId>
      <artifactId>micronaut-inject</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.micronaut</groupId>
      <artifactId>micronaut-validation</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.micronaut.test</groupId>
      <artifactId>micronaut-test-junit5</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.micronaut</groupId>
      <artifactId>micronaut-http-client</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.micronaut</groupId>
      <artifactId>micronaut-http-server-netty</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.micronaut</groupId>
      <artifactId>micronaut-runtime</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>javax.annotation-api</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <scope>runtime</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>io.micronaut.build</groupId>
        <artifactId>micronaut-maven-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <!-- Uncomment to enable incremental compilation -->
          <!-- <useIncrementalCompilation>false</useIncrementalCompilation> -->

          <annotationProcessorPaths combine.children="append">
            <path>
              <groupId>io.micronaut</groupId>
              <artifactId>micronaut-http-validation</artifactId>
              <version>${micronaut.version}</version>
            </path>
          </annotationProcessorPaths>
          <compilerArgs>
            <arg>-Amicronaut.processing.group=hello.micronaut3</arg>
            <arg>-Amicronaut.processing.module=hello-micronaut3</arg>
          </compilerArgs>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>
```

# アプリケーションを書いてみる

こちらを見ながら、簡単なアプリケーションを作ってみましょう。

[Creating a Server Application](https://docs.micronaut.io/3.2.0/guide/index.html#creatingServer)

DIも使っておきます。

[Inversion of Control](https://docs.micronaut.io/3.2.0/guide/index.html#ioc)

Controllerの作成。

```java:src/main/java/hello/micronaut3/HelloController.java
package hello.micronaut3;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import jakarta.inject.Inject;

@Controller("/hello")
public class HelloController {
    @Inject
    MessageService messageService;

    @Get(produces = MediaType.TEXT_PLAIN)
    public String index() {
        return messageService.get();
    }
}
```

Beanの作成。

```java:src/main/java/hello/micronaut3/MessageService.java
package hello.micronaut3;

import jakarta.inject.Singleton;

@Singleton
public class MessageService {
    public String get() {
        return "Hello Micronaut 3!!";
    }
}
```

Micronaut Maven Pluginで起動。

```shell
$ mvn mn:run
```

こんな感じのバナーが表示されます。

```shell
 __  __ _                                  _   
|  \/  (_) ___ _ __ ___  _ __   __ _ _   _| |_ 
| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|
| |  | | | (__| | | (_) | | | | (_| | |_| | |_ 
|_|  |_|_|\___|_|  \___/|_| |_|\__,_|\__,_|\__|
  Micronaut (v3.2.0)

17:13:26.550 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 974ms. Server Running: http://localhost:8080
```

確認。

```shell
$ curl localhost:8080/hello
Hello Micronaut 3!!
```

OKですね。

次は、パッケージングしてJARファイルを作成しましょう。

```shell
$ mvn package
```

起動。

```shell
$ java -jar target/hello-micronaut3-0.1.jar
 __  __ _                                  _   
|  \/  (_) ___ _ __ ___  _ __   __ _ _   _| |_ 
| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|
| |  | | | (__| | | (_) | | | | (_| | |_| | |_ 
|_|  |_|_|\___|_|  \___/|_| |_|\__,_|\__,_|\__|
  Micronaut (v3.2.0)

17:14:18.344 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 957ms. Server Running: http://localhost:8080
```

っていうか、起動速いですね。

> Startup completed in 957ms.

結果は同じなので、割愛。

# ネイティブイメージを作成する

最後に、このアプリケーションをネイティブイメージにしたいと思います。

[Microservices as GraalVM native images](https://docs.micronaut.io/3.2.0/guide/index.html#graalServices)

GraalVMをインストールせずにDockerを使ってビルドする方法もあるようですが、今回はGraalVMをインストールしてビルドすることにします。

GraalVMも[SDKMAN](http://sdkman.io/)でインストール。`native-image`ツールもインストールしておきます。

```shell
$ sdk install java 21.3.0.r11-grl
$ sdk install java 21.3.0.r11-grl
$ gu install native-image
```

GraalVMと`native-image`ツールがインストールされました。

```shell
$ java --version
openjdk 11.0.13 2021-10-19
OpenJDK Runtime Environment GraalVM CE 21.3.0 (build 11.0.13+7-jvmci-21.3-b05)
OpenJDK 64-Bit Server VM GraalVM CE 21.3.0 (build 11.0.13+7-jvmci-21.3-b05, mixed mode, sharing)


$ native-image --version
GraalVM 21.3.0 Java 11 CE (Java Version 11.0.13+7-jvmci-21.3-b05)
```

`-Dpackaging=native-image`を指定して、パッケージング。

```shell
$ mvn package -Dpackaging=native-image
```

だいぶ時間がかかりますが、なんとか完了します。

```shell
[hello-micronaut3:208224]    classlist:   4,339.55 ms,  0.96 GB
[hello-micronaut3:208224]        (cap):     802.08 ms,  0.96 GB
[hello-micronaut3:208224]        setup:   3,730.72 ms,  0.96 GB
Warning: class initialization of class io.micronaut.http.server.$CoroutineHelper$Definition failed with exception java.lang.NoClassDefFoundError: kotlin/coroutines/CoroutineContext. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.micronaut.http.server.$CoroutineHelper$Definition to explicitly request delayed initialization of this class.
[hello-micronaut3:208224]     (clinit):   2,268.13 ms,  3.58 GB
[hello-micronaut3:208224]   (typeflow):  15,282.40 ms,  3.58 GB
[hello-micronaut3:208224]    (objects): 140,670.17 ms,  3.58 GB
[hello-micronaut3:208224]   (features):  25,422.79 ms,  3.58 GB
[hello-micronaut3:208224]     analysis: 192,424.77 ms,  3.58 GB
[hello-micronaut3:208224]     universe:  20,954.42 ms,  3.58 GB
[hello-micronaut3:208224]      (parse):   5,223.66 ms,  3.61 GB
[hello-micronaut3:208224]     (inline):  18,816.18 ms,  3.48 GB
[hello-micronaut3:208224]    (compile):  99,596.81 ms,  3.71 GB
[hello-micronaut3:208224]      compile: 141,904.16 ms,  3.71 GB
[hello-micronaut3:208224]        image:  15,093.41 ms,  3.71 GB
[hello-micronaut3:208224]        write:   3,054.96 ms,  3.71 GB
[hello-micronaut3:208224]      [total]: 382,040.97 ms,  3.71 GB
# Printing build artifacts to: /path/to/target/hello-micronaut3.build_artifacts.txt
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  06:27 min
[INFO] Finished at: 2021-12-03T17:21:50+09:00
[INFO] ------------------------------------------------------------------------
```

ネイティブイメージができあがりました。

```shell
$ file target/hello-micronaut3 
target/hello-micronaut3: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ceca748796f73559894e78c43bb90f0de66038a1, for GNU/Linux 3.2.0, with debug_info, not stripped
```

起動。

```shell
$ ./target/hello-micronaut3 
 __  __ _                                  _   
|  \/  (_) ___ _ __ ___  _ __   __ _ _   _| |_ 
| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|
| |  | | | (__| | | (_) | | | | (_| | |_| | |_ 
|_|  |_|_|\___|_|  \___/|_| |_|\__,_|\__,_|\__|
  Micronaut (v3.2.0)

17:22:42.238 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 63ms. Server Running: http://localhost:8080

```

起動速度がめちゃくちゃ速くなりました。

> Startup completed in 63ms.

動作確認。

```shell
$ curl localhost:8080/hello
Hello Micronaut 3!!
```

## 補足

GraalVMをインストールせずとも、Dockerを使うとネイティブイメージを作れるようです。

```shell
$ mvn package -Dpackaging=docker-native -Pgraalvm
```

この場合、GraalVMのアーティファクトをダウンロードしてきます。

```shell
[INFO] ---------------------------[ docker-native ]----------------------------
Downloading from central: https://repo.maven.apache.org/maven2/org/graalvm/sdk/graal-sdk/21.3.0/graal-sdk-21.3.0.pom
Downloaded from central: https://repo.maven.apache.org/maven2/org/graalvm/sdk/graal-sdk/21.3.0/graal-sdk-21.3.0.pom (1.4 kB at 340 B/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/graalvm/nativeimage/svm/21.3.0/svm-21.3.0.pom
Downloaded from central: https://repo.maven.apache.org/maven2/org/graalvm/nativeimage/svm/21.3.0/svm-21.3.0.pom (1.9 kB at 8.3 kB/s)
...
```

こんなディレクトリツリーになります。

```shell
$ tree $HOME/.m2/repository/org/graalvm -d
$HOME/.m2/repository/org/graalvm
├── buildtools
│   ├── junit-platform-native
│   │   └── 0.9.7.1
│   ├── native-maven-plugin
│   │   └── 0.9.7.1
│   └── utils
│       └── 0.9.7.1
├── compiler
│   └── compiler
│       └── 21.3.0
├── nativeimage
│   ├── objectfile
│   │   └── 21.3.0
│   ├── pointsto
│   │   └── 21.3.0
│   └── svm
│       └── 21.3.0
├── sdk
│   └── graal-sdk
│       └── 21.3.0
└── truffle
    └── truffle-api
        └── 21.3.0

23 directories
```

が、手元の環境の事情で、Dockerイメージを使ってビルドする最中に失敗してしまうので…。

回避方法はまた別途探そうかなと思います。

こんなところで？