Quarkus とは
Red Hat が作っている、最近流行りのマイクロサービス向けの Java アプリケーションフレームワークです。
Grails チームの Micronaut や Oracle の Helidon などと同じ系統です。
公式には以下の特徴が挙げられています。
- コンテナ ファースト - コンテナでの実行に適した低フットプリント
- クラウドネイティブ - Kubernetes などの環境において Twelve-Factor なアーキテクチャ
- Imperative と Reactive の統合
- 標準準拠 - 標準的で良く使われるフレームワークに基づく(RESTEasy, Hibernate, Netty, Eclipse Vert.x, Apache Camel...)
- マイクロサービス ファースト
- 開発者の喜び
Micronaut などと同様に GraalVM を使ってアプリケーションをネイティブイメージにビルドすることができます。
ネイティブイメージ化した際は、ランタイム時のバイトコード操作が行えないなど制限がありますが、Quarkus では、例えば arc という CDI実装を使い、ビルド時にプロキシをDI済みのコード生成することで解決しています(一般的にはランタイム時にプロキシを動的にDI)。
なので、CDI は使い慣れた JavaEE の API を使うことができます。
前置きはこのくらいにして、簡単なアプリケーションの作り方を見ていきましょう。
このガイド
Quarkus による Hello World アプリケーションの作り方を学びます。
このガイドでは以下を学ぶことができます。
- アプリケーションの起動
- JAX-RX エンドポイントの作成
- beans のインジェクション
- Functional tests
- アプリケーションのパッケージング
このガイドを完了するには以下が必要です。
- 15分未満の時間
- お気に入りの IDE
- JDK 1.8 以上がインストールされていること
- Gradle 5.0 以上がインストールされていること
アーキテクチャ
このガイドでは hello
エンドポイントを持つ単純なアプリケーションを扱います。
エンドポイントは依存性の注入により、greeting
ビーンを取り扱うものとします。
Client ---> Greeting Resource ---> Greeting Service
さらにこのガイドではエンドポイントのテストについても扱います。
プロジェクト作成
作業ディレクトリを作成して Gradle init でプロジェクトを作成します。
なお、Quarkus は開発中であり Gradle のサポート状況がまだ良くありません。現時点では Maven を使った方がトラブルが少ないです。
$ mkdir example-quarkus $ cd example-quarkus $ gradle init --type java-application
init タスクによって作成された不要ファイルを削除しておきましょう。
$ rm src/main/java/App.java $ rm src/test/java/AppTest.java
build.gradle
を以下のように編集します。
plugins { id 'java' id 'io.quarkus' version '0.23.2' } repositories { jcenter() } dependencies { implementation enforcedPlatform('io.quarkus:quarkus-bom:0.23.2') implementation 'io.quarkus:quarkus-resteasy' }
io.quarkus
という gradle プラグインを利用しますが、現時点ではこのプラグインは Gradle Plugin Portal に登録されていません。
そのため settings.gradle
に PluginManagement DSL を以下のように定義します。
pluginManagement { repositories { mavenCentral() gradlePluginPortal() } resolutionStrategy { eachPlugin { if (requested.id.id == 'io.quarkus') { useModule("io.quarkus:quarkus-gradle-plugin:${requested.version}") } } } } rootProject.name = 'example-quarkus'
Gradle Plugin Portal に登録されれば、上記定義は不要になります。
エンドポイント リソースの作成
ではエンドポイントを作成しましょう。
$ mkdir -p src/main/java/example/ $ touch src/main/java/example/GreetingResource.java
Quarkus では JAX-RS が使えるので、JavaEE の API を使い以下のように定義します。
package example; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/hello") public class GreetingResource { @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { return "hello"; } }
コードはこれだけです。
開発モードで起動
ではアプリケーションを実行してみましょう。
プラグインにより導入される quarkusDev
タスクを実行します。
./gradlew quarkusDev 2019-00-00 00:00:00,000 INFO [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation 2019-00-00 00:00:00,000 INFO [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 572ms 2019-00-00 00:00:00,000 INFO [io.quarkus] (main) Quarkus 0.23.2 started in 0.983s. Listening on: http://0.0.0.0:8080 2019-00-00 00:00:00,000 INFO [io.quarkus] (main) Profile dev activated. Live Coding activated. 2019-00-00 00:00:00,000 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]
起動は一瞬です。
http://localhost:8080/hello
にアクセスすると以下のようになります。
quarkusDev
で quarkus アプリケーションを起動するとホットデプロイが有効になっています。
Java コードやリソースファイルを更新し、ブラウザでリロードすれば、変更が即時で反映されます。
コンパイルエラー時は以下のように画面から内容が確認できます。
開発モードではデバッガが有効になっているため ポート 5005 でプロセスにアタッチできます。
インジェクションの利用
エンドポイントにサービスをインジェクトして使ってみましょう。
$ touch src/main/java/example/GreetingService.java
以下のようなサービスを定義します。
package example; import javax.enterprise.context.ApplicationScoped; @ApplicationScoped public class GreetingService { public String greeting(String name) { return "hello " + name; } }
エンドポイント側を以下のように変更します。
package example; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/hello") public class GreetingResource { @Inject GreetingService service; @GET @Produces(MediaType.TEXT_PLAIN) @Path("/greeting/{name}") public String greeting(@PathParam("name") String name) { return service.greeting(name); } @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { return "hello!!"; } }
ご覧の通り、CDI のアノテーションでインジェクト処理を行います。
以下のように実行できました。
非同期にする場合は以下のように書くことができます。
@GET @Produces(MediaType.TEXT_PLAIN) public CompletionStage<String> hello() { return CompletableFuture.supplyAsync(() -> { return "hello"; }); }
機能テスト
REST Assured によるテストを行いましょう。
build.gradle
を以下のように更新します。
plugins { id 'java' id 'io.quarkus' version '0.23.2' } repositories { jcenter() } dependencies { implementation enforcedPlatform('io.quarkus:quarkus-bom:0.23.2') implementation 'io.quarkus:quarkus-resteasy' testImplementation 'io.quarkus:quarkus-junit5' testImplementation 'io.rest-assured:rest-assured' } test { useJUnitPlatform() }
quarkus-junit5
と rest-assured
を追加しています。
テストコードを作成していきましょう。
$ mkdir -p src/test/java/example/ $ touch src/test/java/example/GreetingResourceTest.java
以下のようになります。
package example; import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; import java.util.UUID; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.is; @QuarkusTest public class GreetingResourceTest { @Test public void testHelloEndpoint() { given() .when().get("/hello") .then() .statusCode(200) .body(is("hello")); } @Test public void testGreetingEndpoint() { String uuid = UUID.randomUUID().toString(); given() .pathParam("name", uuid) .when().get("/hello/greeting/{name}") .then() .statusCode(200) .body(is("hello " + uuid)); } }
@QuarkusTest
によりテスト実行前にアプリケーションが起動するようになります。
テストを実行してみましょう。
$ ./gradlew test
失敗しましたね。
現時点では Gradle からのテストがエラーになる模様です。公式のリリースをもう少し待ちましょう。
パッケージング
以下のコマンドで Uber jar が作成できます。
$ ./gradlew quarkusBuild --uber-jar
build
配下に jar が作成されるので、以下のコマンドで実行できます。
$ java -jar ./build/example-quarkus-unspecified-runner.jar
GraalVM がインスールされていれば、GRAALVM_HOME
を設定し、以下のコマンドでネイティブイメージが生成できます。
./gradlew buildNative
ネイティブイメージからの起動は爆速になります。
以上、簡単に Quarkus を使ったアプリケーションを作成しました。
次回は JPA の利用について見ていきます。