Qute - 別のテンプレートエンジンが必要な理由

Red Hat で Solution Architect として Quarkus を担当している伊藤ちひろです。

この記事は、Quarkus.ioのブログ記事、Qute - Why (Not Just) Yet Another Templating Engine の翻訳記事です。

Quteは実験的な機能です。

ソリューションが成熟するまで、プラットフォームの安定性や長期的な存在を保証するものではありません。

導入ガイドとより包括的なリファレンスガイドが利用できます。

まず、「なぜまた別のテンプレート・エンジンが必要なのでしょうか?」という良い質問から始めましょう。Javaにはテンプレート・ライブラリがたくさんあります。Quarkusは「優れたライブラリ群と標準」の上に構築することで知られています。その通りです。一方、Quarkusコミュニティーは強力なイノベーションの触媒でもあります。そこで、Quarkusのニーズを満たすために特別に設計されたテンプレートエンジンであるQute (QUarkus TEmplates)を開始することにしました。私たちは、テンプレート化のような開拓された領域にも新しいアイデアをもたらすことができると信じています。

基本的なアイディア

私たちの主な目標は、自説を曲げない革新的なテンプレート・エンジンを提供することです。しかし、車輪を再発明するつもりはありません。代わりに既存の技術に触発されました。いくつか例を挙げてみましょう。

しかし、それだけではありません。Quarkusの原則に基づいた新機能を紹介します...

非同期データの解決 - リアクティブへの道

私たちがQuteの設計を始めたとき、ひとつの重要な側面を念頭に置いていました – データ解決APIは非同期にすべきです。これにより、リソースの使用率が向上し、Quarkusリアクティブモデルに適合します。この設計上の決定のもう1つの結果は、非ブロッキングクライアントをテンプレートから直接利用できることです。つまり、さまざまなソースから非同期にデータを読込めます。

非ブロッキングクライアントのデータ読み込み例

{@org.acme.Client client} ①
<html>
<body>
    <h1>Quarkus Open Pull Requests</h1>
    {#for pull in client.pullRequests} ②
        <p>{pull.title} - {pull.user.login}</p>
    {/for}
</body>
</html>

①パラメータ宣言 - client を org.acme.Client に対応付けます。詳細については、次のセクションを参照してください。

org.acme.Client#getPullRequests()は、ノンブロッキングのVert.xクライアントを使ってGitHub APIから直接データを取得しています。データ解決は非同期なので、スレッドはブロックされず、他のタスクを実行し続けられます。

CompletionStage<JsonArray> getPullRequests() {
   return webClient
            .get(80, "api.github.com", "/repos/quarkusio/quarkus/pulls?state=open&per_page=10")
            .as(BodyCodec.jsonArray())
            .send()
            .thenCompose(r -> {
               if (r.statusCode() == 200) {
                  return CompletableFuture.completedFuture(r.body());
               } else {
                  // Log errors etc.
               }
            });
}

型安全テンプレート

ほとんどのテンプレートエンジンは型安全ではありません。そのため、型のエラーを避けられません。テンプレートの動的性は実用的なものが多いので、ごく自然なことです。一方で、タイプミスや様々なリファクタリングの結果として生じる退屈なエラーからユーザーを守ることはできません。Qute テンプレートは、オプションで型安全にできます。実際にはどんな意味があるのでしょうか?テンプレートは、1つまたは複数のパラメータ宣言を含むかもしれません。パラメータ宣言は、現在の状況で与えられた識別子に具体的な型情報をバインドします。そして、型安全のテンプレートを持つことのメリットは何でしょうか?

  • Quarkusは、パラメータ宣言を参照するすべての式を検証します。無効/不正確な式が見つかった場合、ビルドは失敗します。

開発モードでは、src/main/resources/templates ディレクトリにあるすべてのファイルが変更を監視され、変更はすぐに表示されます。これはまた、型エラーが発生するとアプリケーションが高速に失敗することを意味します。

  • パラメータ宣言で使用されるすべての型に対して値リゾルバが生成され、そのプロパティにリフレクションなしでアクセスできるようになります。これは、GraalVMネイティブイメージをターゲットにする場合に非常に便利です。
  • TODOリストには、型安全な式のパフォーマンスの最適化など、いくつかのアイデアがあります。

型安全のテンプレート例

{@org.acme.Foo foo} ①
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Qute Hello</title>
</head>
<body>
  <h1>{title}</h1><p>{foo.message}</p>③  
  {#for foo in baz.foos}
    <p>Hello {foo.message}!</p>④  
  {/for}
</body>
</html>
  • ①パラメータ宣言 - foo を org.acme.Foo に位置づけます。
  • ②{title} は検証されていません - パラメータ宣言に一致しません。
  • ③{foo.message}は検証されます。org.acme.Fooがプロパティメッセージを持つか、一致するテンプレート拡張メソッドが存在する必要があります。
  • ④{foo.message}は検証されません。なぜならば、ループ部分でfooがオーバーライドされており、型情報が利用できません。

式の中のプロパティだけが現在検証されます。foo.getBar(baz.name)のような "仮想メソッド "は現在無視されます。

ファーストクラスのQuarkus市民

QuteはQuarkusに高度に最適化されているにもかかわらず、コアエンジンはどのような環境にも統合できる独立したライブラリとして開発されています。

Quarkusでは、src/main/resources/templatesディレクトリにあるすべてのテンプレートが検証され、簡単に注入できます。

テンプレート注入例

package org.acme.qute;

import io.quarkus.qute.Template;

class MyBean {

    @Inject
    Template items; 

    @Inject
    Service service;

    String renderItems() {
       return items.data("items", service.getItems()).render(); 
    }
}
  • ①フィールド名はテンプレートを見つけるために使用されます。この特定のケースでは、コンテナは src/main/resources/templates/items.html というパスを持つテンプレートを見つけようとします。利用可能なテンプレートがない場合、ビルドは失敗します。
  • ②基本的な流れについては、Hello Worldの例を参照してください。

さらに、事前に設定されたエンジンのインスタンスが提供され、インジェクションに利用できます。エンジンはテンプレート管理の中心的なポイントであり、いくつかの低レベルAPIを提供しています。

RESTEasyの統合

RESTEasy と一緒に使用すると、リソースメソッドは TemplateInstance を返すだろう。統合コードは必要なすべてのステップを注意して、レスポンスに出力をレンダリングします。詳細については、「RESTEasyとの統合」を参照してください。

JAX-RSリソースの例

package org.acme.qute;
...
import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;

@Path("hello")
public class HelloResource {

    @Inject
    Template hello; ①

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        // the template looks like: Hello {name}!
        return hello.data("name", name);  ②③
    }
}
  • ①フィールド名はテンプレートを見つけるために使用されます。この特定のケースでは、私たちはテンプレートを templates/hello.txt というパスで注入しています。
  • ②Template.data() は、実際のレンダリングが開始される前にカスタマイズできる新しいテンプレートインスタンスを返します。このケースでは、私たちはキー名の下に名前の値を入れています。データマップはレンダリング中にアクセスできます。
  • ③私たちはレンダリングを引き起こさないことに注意してください - これは特別な ContainerResponseFilter の実装によって自動的に行われます。

メーラーの統合

テンプレートは、電子メールメッセージを作成するときに便利かもしれません。Mailer拡張機能はQuteと統合されており、電子メールの便利な送信方法を提供します。特に、メッセージ本文はsrc/main/resources/templatesディレクトリーの*.html*.txtを使用して自動的に作成されます。詳細については、電子メール送信ガイドを参照してください。

まとめ

QuteはQuarkus 1.1 .0で最初に上陸した。それ以来、多くのバグを修正し、いくつかの機能のリクエストを実装しました。APIを安定させ、実装を強固にし、新しい可能性を探るために、私たちのコミュニティに気軽に参加してください!

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。