Quarkus による初めてのアプリケーション作成

f:id:Naotsugu:20191009214703p:plain




Quarkus とは

Red Hat が作っている、最近流行りのマイクロサービス向けの Java アプリケーションフレームワークです。

quarkus.io

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 にアクセスすると以下のようになります。

f:id:Naotsugu:20191007222727p:plain


quarkusDev で quarkus アプリケーションを起動するとホットデプロイが有効になっています。

Java コードやリソースファイルを更新し、ブラウザでリロードすれば、変更が即時で反映されます。

コンパイルエラー時は以下のように画面から内容が確認できます。

f:id:Naotsugu:20191007233556p:plain

開発モードではデバッガが有効になっているため ポート 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 のアノテーションでインジェクト処理を行います。

以下のように実行できました。

f:id:Naotsugu:20191009212816p:plain


非同期にする場合は以下のように書くことができます。

    @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-junit5rest-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 の利用について見ていきます。

blog1.mammb.com