MicronautでPetClinicを実装してNative Image化したら300msくらいで起動したーはやいー

Spring PetClinic をSpringの代わりに Micronaut を使って実装してみた。

github.com

PetClinicなので単純なHelloWorldじゃなくて、ThymeleafとかJPA(Hibernate)とかDIとかを使ってるんだけど、GraalVM のNative Imageでビルドしたら200-400msくらいで起動する。はやいー。

この画像だと324msで起動してる:

Screen Capture

全部実装したわけじゃなくて結構まだぐちゃぐちゃしてるけど、基本的な機能は動くようにしておいた。

## 使い方

Native Imageじゃない場合から

### 1. git cloneして

git clone https://github.com/bufferings/micronaut-petclinic.git
cd micronaut-petclinic

### 2. PostgreSQLを起動して

このPetClinicはPostgreSQLを使う。Docker Composeでデータも用意しておいたのでこれでデータ入りのPostgreSQLが立ち上がる:

docker-compose up

### 3. アプリを実行

./mvnw compile exec:exec

### 3-2. JARを使いたかったらこう

./mvnw package
java -jar target/micronaut-*.jar

### 4. PetClinicにアクセスする

http://localhost:8080/

JARファイルだと僕のノートPCだとだいたい5秒くらいで起動するかな。

## Native Image化

じゃ、Native Imageを作ろう

MicronautがGraalVMのNative Imageのビルドを色々サポートしてくれてるので、このPetClinicもNative Imageとしてビルドして実行できるようにしといた。

./mvnw package && docker build -t micronaut-petclinic .

10分くらいかかるから ☕ 飲みながら待ってる。メモリも4GBくらい使うから、OOMで落ちるときはDockerに対するメモリの割り当て見てみてね。

で、こんな感じで起動する:

# Docker for Mac or Windows
export HOST_NAME=host.docker.internal
# Linux
export HOST_NAME=172.17.0.1

docker run --rm -p 8080:8080 -e JDBC_URL=jdbc:postgresql://${HOST_NAME}:5432/petclinic micronaut-petclinic

300msぐらいで起動するよー (๑•̀ㅂ•́)و✧

まだまだGraalVMもMicronautもこれからって感じだけど、楽しみだなー!

肯定感を持って成長していって欲しい

娘達を見てて、「まだだ!まだ足りない!掛け算はできたけど、割り算はまだ知らないだろう?」って言って育てるよりも「すごい!できた!天才!だが、僕らには大きな目標がある!だから次は割り算やろう!」って言って育てたいと思っている。

何かができなくても「どういうことだ!これ、もう習っただろう?」とか言わずに「ほう・・・今回の相手は手強かったな!仕方がない。次は倒せるようにしておこう!」って言って育てたいと思っている。

だから、それと同じことを、チームと接するときにもやりたいなと思う。できたことをほめる。目的地を共有する。次に進む一歩を一緒に考える。これだけのことなんだけど、娘達に対しても、チームに対しても、そう接するのは、なんか難しい。もっと褒めたい。

あ、ビルド終わった。じゃ!

MicronautでPostgreSQLのリアクティブアクセスをネイティブ化できた

きしださんの記事を見て

nowokay.hatenablog.com

このときはMicronautを使ってもネイティブ化できなかったみたいだけど、今のバージョンならできるんじゃないかなと思ってやってみたら、できたよ。ちょっとごにょっとしたけど。

バージョンはこんな感じ。GraalVMは1.0.0のRC15:

❯ mn --version
| Micronaut Version: 1.1.0
| JVM Version: 1.8.0_202

❯ sdk current java

Using java version 1.0.0-rc-15-grl

コードはここに置いといた。あ、しまった。ネイティブイメージもpushしてしまった。まいっか。

github.com

じゃ、やったことを順番に書いておく。

## アプリを生成

postgres-reactivegraal-native-image をつけてCLIアプリを生成。

❯ mn create-cli-app micronaut-postgres-reactive \
        --features postgres-reactive,graal-native-image

graal-native-image をつけとくと、ネイティブ化用のライブラリーと設定ファイルがついてくる。

具体的には build.gradle にこの2つが入ってくるのと

    annotationProcessor "io.micronaut:micronaut-graal"
    ...
    compileOnly "com.oracle.substratevm:svm"

META-INFの中に

src/main/resources/META-INF/native-image/micronaut.postgres.reactive/micronaut-postgres-reactive-application/native-image.properties

ってファイルが作られてて(長い・・・プロジェクト名を短くすれば良かったな)

内容はこうなってる

Args = -H:IncludeResources=logback.xml|application.yml \
       -H:Name=micronaut-postgres-reactive \
       -H:Class=micronaut.postgres.reactive.Application

## PostgreSQLコンテナ

こんな感じのDocker ComposeでPostgreSQLを用意した。 docker ってディレクトリに入れといた。

docker-compose.yml

db:
  image: postgres:11.2-alpine
  ports:
    - "5432:5432"
  environment:
    - POSTGRES_PASSWORD=mypass
    - POSTGRES_DB=mydb
  volumes:
    - "./initdb.d:/docker-entrypoint-initdb.d"

initdb.d の中にはコンテナを初回起動したときに実行されるSQLを入れて:

CREATE TABLE IF NOT EXISTS users (
  id SERIAL,
  first_name VARCHAR(30),
  last_name VARCHAR(30),
  CONSTRAINT pk_user PRIMARY KEY (id)
);

INSERT INTO users VALUES (1, 'James', 'Carter') ON CONFLICT DO NOTHING;
INSERT INTO users VALUES (2, 'Helen', 'Leary') ON CONFLICT DO NOTHING;
INSERT INTO users VALUES (3, 'Linda', 'Douglas') ON CONFLICT DO NOTHING;
INSERT INTO users VALUES (4, 'Rafael', 'Ortega') ON CONFLICT DO NOTHING;
INSERT INTO users VALUES (5, 'Henry', 'Stevens') ON CONFLICT DO NOTHING;
INSERT INTO users VALUES (6, 'Sharon', 'Jenkins') ON CONFLICT DO NOTHING;

あとコンテナのPrefixを指定したかったので .env も置いといた。

COMPOSE_PROJECT_NAME=micronaut-postgres-reactive

ほんで、起動しとく

❯  docker-compose up

## CLIアプリの実装

これはきしださんのそのまま。DBの情報は少し違うけど。

application.yml

postgres:
  reactive:
    client:
      port: 5432
      host: localhost
      database: mydb
      user: postgres
      password: mypass
      # maxSize: 5

インデントが変なのは、Issueあげといた

https://github.com/micronaut-projects/micronaut-profiles/issues/148

MicronautPostgresReactiveCommand.java

    @Inject
    PgPool client;
    public void data() {
        client.rxQuery("select * from users")
            .map(rowSet -> {
                List<String> result = new ArrayList<>();
                PgIterator ite = rowSet.iterator();
                while(ite.hasNext()) {
                    Row row = ite.next();
                    result.add(row.getString("first_name"));
                }
                return result;
            })
            .blockingGet()
            .stream()
            .forEach(System.out::println);
    }

で実行すると

❯ ./gradlew assemble
...

❯ java -jar build/libs/micronaut-postgres-reactive-0.1.jar
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
James
Helen
Linda
Rafael
Henry
Sharon

OKだね。じゃ、ネイティブ化してみよう

❯ native-image --no-server -cp build/libs/micronaut-postgres-reactive-0.1.jar

## ビルド失敗

Netty周りのエラーが出た。 Unsafe とかのやつ。あれー?なんでだろう?この前見たとき対応入ってたのになー:

bufferings.hatenablog.com

と思ってよく見てみたら、Nettyのネイティブ化用設定が入ったライブラリーがないやないか。入れてみるか。

    compile "io.micronaut:micronaut-http-server-netty"

そしたらビルド通ったや。うーん。

## んで実行!

しても何も出力されない…あれー。なんでだろう?って見てたら。これか…さっきの native-image.properties

Args = -H:IncludeResources=logback.xml|application.yml \
       -H:Name=micronaut-postgres-reactive \
       -H:Class=micronaut.postgres.reactive.Application

エントリーポイントが Application になってるからこれをこう書き換えて:

Args = -H:IncludeResources=logback.xml|application.yml \
       -H:Name=micronaut-postgres-reactive \
       -H:Class=micronaut.postgres.reactive.MicronautPostgresReactiveCommand

ビルドし直したら動いたー

❯ ./micronaut-postgres-reactive
James
Helen
Linda
Rafael
Henry
Sharon

うーん。なんかちょっとイマイチな感じもあるけど。まぁ、動いたからいっかな。

役割分担

後輩たちが、(後輩にとって)新しい技術を導入してて、「動きました!テストも大丈夫でした!バッチリです!」って言ってリリースする。動くんだし、バッチリなんだし、全然問題ない。良いと思う。

のだけど、それを見ながら(たぶん公式ドキュメントの細かいところとか、運用するときに知ってたら楽になることとかは、チェックしてないだろうなぁ)って思って、自分でそういうところが気になるのでチェックしてしまう。

チェックしながら、適当に役に立ちそうなこととか大切そうなことをピックアップしてドキュメントに書き起こしたら「ハンズオンしようか」って言って伝えたりする。そしたら「とても便利な機能があるんですね」とか「ここは気をつけないと危なそうですね」とか言ってくれて僕は満足する。

後輩が、もう一歩踏み込んで調べても良いのになと思わないこともないけど、でも、自分だとそこでそれだけの自信を持ってリリースできないだろうなと思ったり、そんなに嬉しそうにリリースしたりできないだろうなって思ったりして、羨ましかったりする。かっこいいよなー。

まぁ、役割分担ってことだな。

MicronautアプリをIntelliJ IDEAから実行しようとしたときにApplicationContextBuilderが見つからないって言われたら

Micronaut 1.1.0 で mn create-app を使ってアプリを生成して、IndelliJ IDEAに取り込んで、Annotation Processorの設定もして、下の画像のあたりを押して Application.java から実行しようとしたら

f:id:bufferings:20190421173209p:plain

こんなエラーが出て実行できない (´・ω・`)ショボーン

Exception in thread "main" java.lang.NoClassDefFoundError: io/micronaut/context/ApplicationContextBuilder
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
    at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:151)
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:802)
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:700)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:623)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    at hello.mn.Application.main(Application.java:8)
Caused by: java.lang.ClassNotFoundException: io.micronaut.context.ApplicationContextBuilder
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    ... 10 more

Process finished with exit code 1

対応はここに書いてた。

https://github.com/micronaut-projects/micronaut-core/issues/685#issuecomment-482415658

Run > Edit Configurations... から「Include dependencies with "Provided" scope」にチェックを入れたら動いた。

f:id:bufferings:20190421173922p:plain

17:41:09.521 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 3485ms. Server Running: http://localhost:8080

(∩´∀`)∩ワーイ

Micronaut 1.1.0 からは、Native Imageのビルドが簡単になってたよ

## 何も指定せずにNative Imageをビルドしても動くのなんでだろう?

この前、Micornautのアプリをnative-imageでビルドして遊んで

bufferings.hatenablog.com

その後に、Micronautとは関係なく、Nettyをnative-imageでビルドして遊んで

bufferings.hatenablog.com

ふと「あれ?MicronautもNetty使ってるはずなんだけど、何も指定せずにNative Imageをビルドしても動くのなんでだろう?」と思って色々見て回ったメモ。

## build-native-image.sh がなくなってる

きしださんのブログや

nowokay.hatenablog.com

kencharosさんのQiita記事を見てると

qiita.com

MicronautがNative Imageビルド用の build-native-image.sh というファイルがあったみたい。なんだけど、僕の生成したプロジェクトの中には見当たらない。

探してみたら、1.1.0.M2までは提供していたんだけど、その次のリリースで削除されたみたいね。

https://github.com/micronaut-projects/micronaut-profiles/blob/v1.1.0.M2/base/features/graal-native-image/skeleton/gradle-build/build-native-image.sh

build-native-image.sh の中身はこうなってる

./gradlew assemble
java -cp build/libs/@app.name@-*.jar io.micronaut.graal.reflect.GraalClassLoadingAnalyzer
native-image --no-server \
             --class-path build/libs/@app.name@-*.jar \
             -H:ReflectionConfigurationFiles=build/reflect.json \
             -H:EnableURLProtocols=http \
             -H:IncludeResources="logback.xml|application.yml" \
             -H:Name=@app.name@ \
             -H:Class=@defaultPackage@.Application \
             -H:+ReportUnsupportedElementsAtRuntime \
             -H:+AllowVMInspection \
             --allow-incomplete-classpath \
             --rerun-class-initialization-at-runtime='sun.security.jca.JCAUtil$CachedSecureRandomHolder,javax.net.ssl.SSLContext' \
             --delay-class-initialization-to-runtime=io.netty.handler.codec.http.HttpObjectEncoder,io.netty.handler.codec.http.websocketx.WebSocket00FrameEncoder,io.netty.handler.ssl.util.ThreadLocalInsecureRandom,com.sun.jndi.dns.DnsClient

それが、現在の最新版の1.1.0ではこんな感じでビルドできる

./gradlew assemble
native-image --no-server -cp build/libs/hello-graal-*.jar

シンプルになったね。だから build-native-image.sh は削除したってことみたい。いいね。でも、じゃあ GraalClassLoadingAnalyzer とか、native-imageのオプションはどこにいったんだろう?

## native-image.properties

mn create-app をするときに --features graal-native-image オプションをつけると、こういうファイルが生成される。(アプリ名は hello-graal にしてる)

❯ cat src/main/resources/META-INF/native-image/hello.graal/hello-graal-application/native-image.properties
Args = -H:IncludeResources=logback.xml|application.yml \
       -H:Name=hello-graal \
       -H:Class=hello.graal.Application

ふむ。この native-image.properties について調べると、ロジコさんが翻訳してくれてるこの記事の後半に書いてあった。

medium.com

GraalVMの最近のリリース以降、ネイティブイメージのビルドについて別の重要な改善をしています。JARファイル中のMETA-INF/native-imageにnative-image.propertiesファイルを埋め込むことができるようになりました。native-imageツールは、このリソースの場所にあるすべてのファイルを自動的に処理し、それらを使用してnative-imageのコマンドライン引数を作成します。

なるほど。

GraalVMのドキュメントを探してみたけど native-image.properties についての記述は見つからなかったので、ソースを見てみた。たぶんここかなぁ。

https://github.com/oracle/graal/blob/vm-1.0.0-rc15/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java#L762-L787

META-INF/native-image ディレクトリーの中で native-image.properties って名前で終わってるファイルをチェックしてるみたいね。

## ということは、Nettyは?

と思ってJARファイルの中を見てみたら、あった。そういうことかー。

f:id:bufferings:20190421131400p:plain:w300

## で、その元になってるファイルはどこにあるん?

と思って、JARを見たら micronaut-http-netty-1.1.0.jar の中に入ってた。

f:id:bufferings:20190421141215p:plain:w600

native-image.properties で「自分と同じディレクトリーにある reflection-config.json を読み込んでくれー」って言ってる。

## なんとなく、ソースも見たい

と思って探してみた。これだな。

https://github.com/micronaut-projects/micronaut-core/blob/v1.1.0/http-netty/src/main/java/io/micronaut/http/netty/channel/NettyThreadFactory.java#L40-L46

@TypeHint(value = {
        NioServerSocketChannel.class,
        NioSocketChannel.class
}, typeNames = {"sun.security.ssl.SSLContextImpl$TLSContext", "sun.nio.ch.SelectorImpl"},
   accessType = {TypeHint.AccessType.ALL_DECLARED_CONSTRUCTORS, TypeHint.AccessType.ALL_DECLARED_FIELDS}
)
public class NettyThreadFactory {

@TypeHint アノテーション@Introspected アノテーションがついてると、Native Image用のプロパティファイルを生成するみたい。

このアノテーションの処理はコンパイルタイムに GraalTypeElementVisitor がやってるっぽいね。

https://github.com/micronaut-projects/micronaut-core/blob/v1.1.0/graal/src/main/java/io/micronaut/graal/reflect/GraalTypeElementVisitor.java#L90

この仕組みができたから GraalClassLoadingAnalyzer の役目は終了したっぽい。 1.1.0 では削除されてる。

## Substitutionsに関して

は、ここだな。この前Netty試したときに書いたやつがそのまま書いてある。

https://github.com/micronaut-projects/micronaut-core/blob/v1.1.0/http-netty/src/main/java/io/micronaut/http/netty/graal/MicronautSubstitutions.java

## ライブラリー側でnative-image対応が可能になる

ロジコさんの記事に戻って見てみると、こう書いてある。

正しく適用すれば、Javaライブラリの作者は上述のメカニズムを使ってライブラリにnative-image互換性を持たせることができます。

なるほどなぁ。

## 気になるのは

こんな風に native-image.properties に書いてある場合

https://github.com/micronaut-projects/micronaut-core/blob/master/inject/src/main/resources/META-INF/native-image/io.micronaut/inject/native-image.properties

Args = -H:+ReportUnsupportedElementsAtRuntime \
       --allow-incomplete-classpath

知らないうちにオプションが適用されてしまってるんだろうなぁというところかな。

あと、Nettyのログは java.util.logging に限定されることになるんかな?(よく分かってない

## まとめ

Micronaut 1.1.0 からは、Native Imageのビルドが簡単になってたよ。面白かった。

僕らのモブプログラミングは「全員でプログラミングをする」ということではなかった

## 去年の夏ぐらいからサポートしているチーム

で、それまでもちょこちょこモブプログラミングを試してはいたんだけど、3月からは思い切ってそれを基本として開発をするようにした。つまり、3月からは1日中モブプログラミングをするのを毎日やってる。

プログラミングだけじゃなくて、設計も、運用も、テストも、全部モブでやってるので、僕らはそれをモブワークと呼んでる。

## やっていく中で学んだのは

モブプログラミング(モブワーク)は「全員でプログラミングをする」ということではなくて「全員で考えて取り組む」というだけのことだった。

サービスにとってどう動くのが良いかを全員で考える。

目の前のプロジェクトのことだけではなく、少し先を見据えてメンバー間の知識やスキルの共有や、チームがまだ詳しくない分野の学習をすることも含めて、どこにトレードオフスライダーをセットするのが良いかを全員で考える。

## 全員でプログラミングをするというのは

その手段のひとつに過ぎない。だから、そのチームは常に全員で作業をしている訳ではなくて、状況に応じて、全員モブ(5人)、モブ&ペア(3+2)、モブ&ソロ(4+1)、ペアペアソロ(2+2+1)などを切り替えながら開発を進めている。

2月までと違うのは「全員モブがデフォルト」ということだ。それまでは「全員別々がデフォルト」で、その中でペアやモブを組んでやっていた。でも、今はまず全員が集まることが基本で、そこから必要に応じて「ペアにわかれようか」とかの話をしているのだ。

外から見るとどちらもペアでやっているのだけど、実際に中にいると全く情報の流れが違う。

## 全員で毎日ふりかえって

次の日にトライする。その結果を見てまた次の日に別のやり方を試してみる。そうやって、自分たちに合った形の働き方を探っている。

例えば、そのチームはランチをとる時間がメンバーによって違うのだけど、最初はランチ時間を揃えてみて「まぁ、できなくはない。でもランチは自由に取りたい」って話がでて、じゃあ、ってことで好きな時間に取るようにしたら、今度は「誰かがランチに行ってる間は作業が止まってしまう」ってなって、じゃあ全員が揃ってなくても二人いたら進めようか、ってなって、今はランチの時間も作業が止まらずに進んでいる。

## チームはスクラムの中でモブワークをやっている

ので、2週間のスプリントがある。2週間の初日にスプリントレビュー・スプリントレトロスペクティブ・スプリントプランニングをやって、2日目から10日目まで開発をする。真ん中ぐらいでバックログリファインメントがある。3スプリント回してきて、だんだん自分たちのチカラをスムーズに発揮できるようになってきたと感じる。

## 1日の流れ

は、こんな感じ。

  • 9:00-10:00 学習セッション
  • 10:00-15:30 モブワーク
  • 15:30-16:30 個人の時間
  • 16:30-17:00 今日のレビュー・今日のふりかえり・明日のプランニング

## 良いリズムをつくりだしているのは

毎日のふりかえりだろうな。「何かもやっとする・・・」というときにそれをふせんに書いて口に出しても良い。誰もそれに対して否定をしない。という安心感がチームの中にある。

もう僕のサポートが要らないチームになってしまって、少し寂しい気持ちもあるけど、何か問題にぶつかっても、メンバー全員で意見を出し合って乗り越えていっているのを見ていると、とてもうれしい。