Quarkus入門

Quarkusとは

Redhat社製Java言語でWebServices/Microservicesを作る為のフレームワークである。起動が素早くてパッケージのサイズが小さくてContainer化されたアプリでは大きなメリットを持つ。まして、JavaEEのいくつかの標準技術(JAX-RS/JSON-B/CDIなど)を取り入れているから新しいことを学ぶ必要が殆どない。JavaVM上で実行可能の他にGralVMでも実行できる。従来のJavaEE Containerサーバーに比較して爆速。GralVMを用いた場合はAOTコンパイルが出来て実行速度が更に増し。

本記事で使っているコードは全て こちらから ダウンロード可能

アプリを作成する

公式なサイトと同様に maven を利用して以下のコマンドで基本的なアプリが作成可能

mvn io.quarkus:quarkus-maven-plugin:1.1.0.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=getting-started \
    -DclassName="org.acme.quickstart.GreetingResource" \
    -Dpath="/hello"
cd getting-started

できたもの

  • JAX-RSを使ったWebApp基本的なプロジェクト.
  • JavaEEのアプリと同様にresourcesフォルダー内にhtmlなどのファイルも配置可能で staticファイルをserve可能。
  • Dockerでデプロイできる様にDockerfile作成済
  • mavenプロジェクトで .gitignore と.dockerignoreファイルがあり、好みのIDEですぐに開発しやすい様になっている。
  • testクラス実装サンプル

起動

./mvnw compile quarkus:dev:

好みのIDEで起動

  • 私は前IntellJがよかった。有料バージョンだともっと凄そうだけど 今回visual codeでやってみて非常に良かっくて推奨。
  • 以下のプラグインがインストールされているとjava環境として十分
  • getting-startedというフォルダーをvisual codeで起動するだけで サーバーが起動され すぐに開発が可能
  • localhost:8080にアクセスするとデフォルトのランディングページが表示される。

エンドポイントを実装する

公式のサイトにも紹介されているが 極普通のJAXRSの書き方が可能

@Path("/hello")
public class GreetingResource {
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello";
    }
}

CDIも記述可能。まずDependencyのクラスを実装し

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class GreetingService {

    public String greeting(String name) {
        return "hello 2 " + name;
    }

}

次にResourceクラスにそのDependencyをInjectし、エンドポイントで利用する。

@Path("/hello")
public class GreetingResource {

    @Inject
    GreetingService service;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/greeting/{name}")
    public String greeting(@PathParam("name") final String name) {
        return service.greeting(name);
    }
}

叩いて見る

$ curl localhost:8080/hello
hello
$ curl localhost:8080/hello/nacho
hello 2 nacho

エンドポイントをテスト

テストサンプルも提供されているので 上記のエンドポイントのテストを書くことが可能。 ポイントは@QuarkusTestである。このアノテーションがあるとアプリを先に起動してエンドポイントを叩ける状態にしてくれる。

提供されているサンプルコードでRestAssuredとHamcrestのフレームワークが使用されているが別のものも使用することが可能。

@QuarkusTest
public class GreetingResourceTest {

    @Test
    public void testHelloEndpoint() {
        given().when().get("/hello").then().statusCode(200).body(is("hello"));
    }

    @Test
    public void testGreetingPoint() {
        final String uuid = UUID.randomUUID().toString();
        given().pathParam("name", uuid).when().get("/hello/greeting/{name}").then().statusCode(200)
                .body(is("hello 2 " + uuid));
    }

Visual Codeで テストファイルを開いて各テスト関数の箇所に「Run Test | Debug Test | ✔︎」マークが表示され ワンクリックで単独のテストを実行可能

visualcode-test.png

JSONを返却するエンドポイントを実装

プロジェクトを作成した時に -Dextensions を指定しなかったからデフォルトで quarkus-resteasyというextensionだけ追加された。オブジェクトをJSONに変換する為に quarkus-resteasy-jsonb というextensionを追加する必要がある。

既存のプロジェクトの直下で以下のコマンドで追加可能

./mvnw quarkus:add-extension -Dextensions="quarkus-resteasy-jsonb"

内部的にpom.xmlファイルに必要なdependencyが追加されることが分かる。

</dependencyManagement>
  <dependencies>
    ...
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jsonb</artifactId>
    </dependency>
   ...

ちなみに、沢山のextensionが存在し以下のコマンドで一覧を閲覧可能

./mvnw quarkus:list-extensions

jsonbのextension追加されたあとに オブジェクトを定義して

public enum BaseResult {
    OK,
    NG
}
public class BaseResponse {
    public BaseResult result;
    public String message;
    public BaseResponse() {
        result = BaseResult.OK;
        message = "";
    }
    public BaseResponse(BaseResult r, String m) {
        result = r;
        message = m;
    }
}

そしてリソースクラスでそのクラスのオブジェクトを返却するメソッドと定義可能

@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/json")
public BaseResponse jsonSync() {
    BaseResponse r = new BaseResponse(BaseResult.OK, "Everything is fine");
    return r;
}

そのテストも実装可能

@Test
public void testJsonSyncPoint() {
    final BaseResponse expected = new BaseResponse(BaseResult.OK, "Everything is fine");
    final String expString = JsonbBuilder.create().toJson(expected);
        given().when().get("/hello/json").then().statusCode(200).body(is(expString));
}

JSON-Bのビルダークラスを利用して期待オブジェクトをString型に変換してから比較など可能

非同期処理

エンドポイントのメソッドがCompletableStageを返却すると自動的に非同期処理になる。

@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/json/async")
public CompletionStage<BaseResponse> demand() {
    return CompletableFuture.supplyAsync(() -> {
        BaseResponse r = new BaseResponse(BaseResult.OK, "Everything is fine");
        return r;
    });
}

非同期処理でも同期処理でもテストは同じやり方で実装

@Test
public void testJsonAsyncPoint() {
    final BaseResponse expected = new BaseResponse(BaseResult.OK, "Everything is fine");
    final String expString = JsonbBuilder.create().toJson(expected);
        given().when().get("/hello/json/async").then().statusCode(200).body(is(expString));
}

フロントエンドを実現

index.htmlと同様に htmlや他のリソースファイルを配置することが可能。 英語の公式のガイドfruits.htmllegume.html を実装している。内部的に javascriptで自分のエンドポイントを呼び出しHTMLに表示する。