Quarkusのテンプレートエンジン "qute"

この記事は赤帽エンジニア Advent Calendar 2019の12/11分の記事です。

今日は日本JBossユーザグループでQuarkus入門について話しましたが、スライドはこちらです。今日の記事では、スライドに入りきらなかった qute について紹介します。


背景

今年の4月にGlassFishユーザ会に向けてQuarkus0.14.0を触りながら、実際の開発で使うならば欲しいと思った機能が2つありました。

  • Hibernate以外のDBアクセスの選択できること
    • スキーマが複雑になるほどSQLを直接書きたいユーザは多い
  • Thymeleafのようなサーバサイドで使えるテンプレートエンジン

DBアクセスについては、Quarkus自体のアーキテクチャがVert.xベースになったことにより、スライドでも言及している Quarkus - Reactive SQL Clients が導入され、元々はノンブロッキングなDBアクセスを目的とする機能ですが、APIがJdbcTemplateに似てるのでこれで良いなと。

もう一つのテンプレートエンジンは、近年はフロントエンドは別に作り込まれる方が一般的になってきている一方で、エンタープライズなアプリではサーバサイドテンプレートでも十分機能するのでは、ちょっとした社内システムにそこまでフロントエンド必要なのかなと思っていま..(ごめんなさいクライアントサイドも勉強します)

テンプレートエンジン "qute"

quteはQuarkus向けに新たに開発された、Graal VMのnative-imageコマンドによるネイティブビルドに対応したテンプレートエンジンです。先日、Quarkusのmasterブランチにマージされましたが、まだ最新のリリース版であるQuarkus1.0.1には含まれていません。

シンプルな機能のみを提供しており { name } のように {} で囲まれた部分をテンプレートとして置き換えます。 以降にサンプルを紹介します。

qute Hello, world!

以下はテンプレート部分です。ソースコードsrc/main/resources/templates に以下のようなhello.htmlを配置します。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello Quarkus Qute template engine</title>
</head>
<body>
    <h1>Hello Quarkus Qute template engine</h1>
    <p>Hello, {name}!</p>
</body>
</html>

JAX-RSのエンドポイント実装からテンプレートを解決したhtmlを応答したい場合は、以下のように書きます。

import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;

import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;

@Path("hello")
public class HelloResource {

    @Inject
    Template hello;   // src/main/resources/templates/hello.html の拡張子を除いた hello に合わせる

    @GET
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance get(@QueryParam("name") String name) {
        return hello.data("name", name);  // 第1引数はテンプレートにかかれたキー name, 第2引数はテンプレートに適用する値
    }
}

コードの説明はコメントの通りで、シンプルに扱えることがわかります。

繰り返し each / 条件分岐 if

以下の例のように繰り返しやifなどの最低限の機能は満たされています。

<p>List of items over 100 yen</p>
<ul>
    {#each items}
        {#if it.price > 100}
            <li>name: {it.name} , price: {it.price}</li>
        {/if}
    {/each}
</ul>

{#each}には、Iterable、MapのentrySet、Streamインスタンスが設定できるようになっています。it.nameのitはループ中の現在の要素を示しており、it.<プロパティ名>でItemクラスのnameプロパティにアクセスしています。

java側のコードは以下の通りです。

public class Item {
    private String name;
    private int price;
    // getter & setter 省略
}

import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;

import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.Arrays;
import java.util.List;

@Path("items")
public class ItemResource {

    @Inject
    Template items;

    @GET
    @Path("/all")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance getAll() {
        return items.data("items", getSnacks());
    }

    private List<Item> getSnacks() {
        Item ice = Item.of("ice cream", 120);
        Item cookie = Item.of("cookie", 80);
        Item cake = Item.of("cake", 200);
        Item chocolate = Item.of("chocolate", 90);
        return Arrays.asList(ice, cookie, cake, chocolate);
    }
}

GitHub上に上記のコードを置いています quarkus-qute-helloworld
本日時点はまだないですが、正式版がリリースされているときには quarkus-quickstarts にサンプルコードが他の機能と同様に用意されるはずです。

"qute" を実際に試す

quteはまだリリースされていないため、試すためにはquarkusのmasterブランチを自分でビルドします。テストをスキップするとGraalVMを用意しなくてもOpenJDK8でビルド可能です。

git clone https://github.com/quarkusio/quarkus.git
cd quarkus
mvnw clean package -DskipTests=true

自分でビルドしたmasterブランチのQuarkus(バージョン999-SNAPSHOT)をアプリケーションで利用するときに少し注意が必要です。
mvn io.quarkus:quarkus-maven-plugin:1.0.1.Final:createで生成したひな型プロジェクトでは、pom.xmlにおいてquarkus-universe-bomがbomが参照されていますが、前述ののビルドではquarkus-universe-bomはローカル上の~/.m2生成されていません。自分でビルドしたQuarkusでは、ビルド時に生成されるquarkus-bomに書き換えます。

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        ...
    <properties>
        ...
        <quarkus-plugin.version>999-SNAPSHOT</quarkus-plugin.version>        <!-- バージョンを999-SNAPSHOTに書き換える -->
        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>         <!-- quarkus-universe-bom から書き換える -->
        <quarkus.platform.version>999-SNAPSHOT</quarkus.platform.version>  <!-- バージョンを999-SNAPSHOTに書き換える -->
       ....

上記については公式ガイドに言及があります。

quarkus-qute-helloworldに置いたコードでは、pom.xmlの修正は既に盛り込まれています。これでローカル環境でquarkus-qute-helloworldmvn clean package でビルドして試せるようになりました。

まとめ

  • quteはQuarkusにこれから盛り込まれる予定のネイティブコンパイルに対応したテンプレートエンジンです
  • { }で囲った部分をテンプレートして置換します
  • Quarkus1.0おめでとう!