はじめに
マイクロサービス・アプリケーション・フレームワークである Quarkus の バージョン 1.1 が 2019年12月17日にリリースされました。
主な変更点は以下となります。
- GraalVM は 19.2.1 をサポート
- GraalVM 19.3 へのアップグレードはいくつか問題があり Quarkus 1.2 に持ち越し
- 新しいテンプレートエンジン Qute を experimental(実験的)として追加
- Configuration フレームワークの更新
- quarkus-config-yaml 拡張で YAML 形式をサポート
- Spring API のサポートに Spring Security API を追加
- Gradle のバージョンを 6系に変更
- 旧来ユーザは Gradle plugin の宣言を変更する必要あり
- Logging の改善
- logging-gelf 拡張で Graylog Extended Log Format(GELF)をサポート
- logging-json 拡張で JSON 形式のログフォーマットをサポート
- logging-sentry 拡張で Sentry によるイベント監視をサポート
- Kogito 0.6 へのアップデート
- Kogito はルール/プロセスエンジン
- Quartz 拡張にクラスタ化されたJOBのサポートを追加
細かな変更が多々入っています。
本稿では、Quarkus 1.1 で追加されたテンプレートエンジン Qute の概要を見ていきます。
Qute とは
おそらく、QUarkus TEmplate で Qute という名前になっているのだろうと思います。
Quarkus のアプローチに沿った、新しいテンプレートエンジンで、コードジェネレートによりビルド時に可能なことはビルド時に行い、リフレクションの使用を最小限に抑え、ネイティブイメージのサイズは小さく保つようになっています。
Quarkus 1.1 では experimental(実験的) 機能としてリリースされました。 初期コミットは2019年5月30日であり、現時点では初期の開発段階にあります。
プロジェクトの作成
既にスターターページに反映されています。
Build Tool は Gradle を選択し、以下の拡張を選択してプロジェクトファイルをダウンロードします。
せっかくなので、Quarkus 1.1 で入った quarkus-config-yaml も入れておきました。YAMLでの設定も合わせて見ていきます。
ダウンロードしたファイルを適当なディレクトリに解凍し、中身を覗いてみましょう。
build.gradle
は以下のようになっています。
plugins { id 'java' id 'io.quarkus' } repositories { mavenLocal() mavenCentral() } dependencies { implementation 'io.quarkus:quarkus-resteasy-qute' implementation 'io.quarkus:quarkus-config-yaml' implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") implementation 'io.quarkus:quarkus-resteasy' testImplementation 'io.quarkus:quarkus-junit5' testImplementation 'io.rest-assured:rest-assured' } group 'org.acme' version '1.0.0-SNAPSHOT' compileJava { options.compilerArgs << '-parameters' } java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 }
gradle.properties
は以下のようになっています。
quarkusPluginVersion=1.1.0.Final quarkusPlatformArtifactId=quarkus-universe-bom quarkusPlatformVersion=1.1.0.Final quarkusPlatformGroupId=io.quarkus
Hello Qute
src/main/java/org/acme/ExampleResource.java
を以下のように変更します。
package org.acme; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import io.quarkus.qute.TemplateInstance; import io.quarkus.qute.Template; @Path("/hello") public class ExampleResource { @Inject Template hello; @GET @Produces(MediaType.TEXT_PLAIN) public TemplateInstance get(@QueryParam("name") String name) { return hello.data("name", name); } }
Template hello
とすることで、フィールド名と同名のテンプレートファイルが src/main/resources/templates/
から選択されます。
@ResourcePath
アノテーションでリソースパスを指定することもできます。
たとえば以下のようにするとsrc/main/resources/templates/example/items.html
がテンプレートファイルとして選択されます。
@ResourcePath("example/items.html") Template hello;
テンプレートファイル自体を作成します。
src/main/resources/templates/hello.txt
に以下の内容でファイルを作成します。
Hello {name}!
hello.data("name", name);
のようにして値の埋め込みができます。
YAML サポートも試しておきます。
src/main/resources/application.properties
を src/main/resources/application.yaml
にリネームして以下のように編集します(application.properties
が優先されるためリネームしておく必要があります)。
quarkus: http: port: 9090
ここでは YAML 形式でポートを指定しました。
実行してみましょう。
$ ./gradlew quarkusDev
http://localhost:9090/hello?name=Bob
にアクセスすれば以下のような出力が得られます。
簡単ですね。
Qute テンプレートの文法
式はブランケットで括ります。
{name} {item.name}
現在のコンテキストから名前で解決され、解決できない場合は(もしあれば)親のコンテキストオブジェクトで解決が試みられます。
メソッド呼び出しは以下のようになります。
{item.get(name)} {name or 'John'},
2つ目の例は、暗黙的に name.or('John')
というメソッド呼び出しに変換されます。
条件分岐は以下のようになります。
{#if item.name is 'sword'} It's a sword! {#else if item.name is 'shield'} It's a shield! {#else} Item is neither a sword nor a shield. {/if}
is
は eq
や ==
としても同じです。その他、ne
, !=
, gt
, >
, ge
, >=
, lt
, <
, le
, <=
が利用できます。
each ループは以下のように書きます。
{#each items} {count}. {it.name} {/each}
count
は1始まりのカウンタとして使用できます。
it
はループ対象のエイリアスとして使用できます。
for ループは以下のように書きます。
{#for item in items} {item.name} {/for}
with で現在のコンテキストオブジェクトを定義できます。
{#with item.parent} {name} {/with}
この例では、item.parent.name
として解決されます。
include にて共通のテンプレートに値を流し込むことができます。
共通テンプレートとして base
を以下のように準備し、
<html> <head> <meta charset="UTF-8"> <title>{#insert title}Default Title{/}</title> </head> <body> {#insert body}No body!{/} </body> </html>
それぞれのテンプレートで以下のようにすれば、
{#include base} {#title}My Title{/title} {#body} <div> My body. </div> {/body} {/include}
以下のような出力が得られます。
<html> <head> <meta charset="UTF-8"> <title>My Title</title> </head> <body> <div> My body. </div> </body> </html>
Template Extension Methods
Qute には Template Extension Methods という便利な仕組みが用意されています。
Hello World より少し複雑な例の中で動作を見ていきます。
最初にテンプレートに出力するための Item を以下のように用意します。
package org.acme; import java.math.BigDecimal; public class Item { public String name; public BigDecimal price; public static Item of(String name, BigDecimal price) { Item item = new Item(); item.name = name; item.price = price; return item; } }
YAML 設定の例も含めたいので application.yaml
を以下のように変更します。
quarkus: http: port: 9090 app: items: title: Item list
Item 一覧のタイトルを定義しました。
ItemResource
を以下のように作成します。
package org.acme; import io.quarkus.qute.Template; import io.quarkus.qute.TemplateExtension; import io.quarkus.qute.TemplateInstance; import org.eclipse.microprofile.config.inject.ConfigProperty; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import java.math.BigDecimal; import java.util.Arrays; @Path("items") public class ItemResource { @ConfigProperty(name = "app.items.title", defaultValue = "Title") String title; @Inject Template items; @GET @Path("/") @Produces(MediaType.TEXT_HTML) public TemplateInstance list() { return items .data("title", title) .data("items", Arrays.asList( Item.of("item1", BigDecimal.valueOf(100)), Item.of("item2", BigDecimal.valueOf(200)), Item.of("item3", BigDecimal.valueOf(300))) ); } @TemplateExtension static BigDecimal discountedPrice(Item item) { return item.price.multiply(new BigDecimal("0.9")); } }
@ConfigProperty
で YAML 定義内容を取り込んでいます。
@Produces(MediaType.TEXT_HTML)
で HTML を返却するメソッドを定義し、Item のインスタンスをリストでテンプレートに流しています。
@TemplateExtension
で指定したメソッドが Template Extension Methods で、テンプレートからこのメソッドを呼び出すことができます。
この例では、Item の割引した価格を計算する static メソッドとなります。
最後にテンプレート items.html
です。
<!DOCTYPE html> <html> <head> <title>{title}</title> </head> <body> {#for item in items} <h5>{item.name}</h5> <p> Price: {item.price} <br/> {#if item.price > 100} Discounted Price: {item.discountedPrice} {/if} </p> {/for} </body> </html>
Item の内容を for ループで繰り返しています。
{item.discountedPrice}
で先程定義した Template Extension Methods の呼び出しとなります。
item
がベースオブジェクトとなり discountedPrice
という名前のメソッドに渡されることになります。これにより Item オブジェクトに discountedPrice
という computed properties を定義したように動きます。
実行してみましょう。
$ ./gradlew quarkusDev
http://localhost:9090/items
にアクセスすれば以下のような出力が得られます。
まとめ
Quarkus 1.1 で追加されたテンプレートエンジンである Qute について見てみました。
まだ初期の開発段階ではありますが、シンプルで心地よいテンプレートエンジンでした。
- 作者:Sam Newman
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/02/26
- メディア: 単行本(ソフトカバー)
Kubernetes完全ガイド impress top gearシリーズ
- 作者:青山 真也
- 出版社/メーカー: インプレス
- 発売日: 2018/09/21
- メディア: Kindle版
Microservices for the Enterprise: Designing, Developing, and Deploying (English Edition)
- 作者:Kasun Indrasiri,Prabath Siriwardena
- 出版社/メーカー: Apress
- 発売日: 2018/11/14
- メディア: Kindle版