Adding Eyecandy to your Quarkus App with React and Patternfly

コンテナ化された分散型のQuarkusアプリケーションで一般的なパターンとして、別のフロントエンドアプリケーションのための効率的でスケーラブルなバックエンドとして機能することがあります。Kubernetesの世界では、フロントエンドアプリケーションはReact、Angular、Vue.jsなどの純粋なウェブレイヤーPodであり、すべてのRESTコールをQuarkusのサービスレイヤーPodに委ねることができます。

frontend pod pattern

このアプローチには、独立したコンテナのスケーリングと独立したライフサイクルという利点がありますが、小規模なアプリケーションには過剰なエンジニアリングとなる場合があります。

最新のWeb GUI技術に妥協することなく、自己完結型の小さなCRUDアプリケーションを作りたいと思っているのではないでしょうか? Angularでこれを実現する方法を見てきましたが、少なくともGitHubの星の数において、人気の点でAngularを上回っているReactはどうでしょうか?今回は、Reactを統合するだけでなく、 Patternflyと呼ばれるWebコンポーネントフレームワークを使用する方法を紹介します。Patternflyは、リッチで一貫性のあるインタラクティブなGUIを作成するためのパターン、コンポーネント、スタイルを集めたものです。

React/Patternflyのテーブルに表示される原子パーティクルオブジェクトを提供するRESTエンドポイントを持つQuarkus Appを作成してみましょう。最終的なアプリケーションのソースコードは こちらにあります。

ステップ1 - Quarkusプロジェクトの生成

スタータープロジェクトを生成するには、 https://code.quarkus.io にアクセスします。Maven Groupとして "io.quarkus" 、Artifactとして "quarkus-react" を入力します。エクステンションのリストで、 RESTEasy JAX-RSRESTEasy JSON-B を選択します。その後、プロジェクトを生成してダウンロードし、ローカル・マシンで解凍して、お気に入りのIDEでプロジェクト・フォルダを開きます。

ステップ2 - Quarkus RESTエンドポイントの追加

サンプルの GreetingResource.java が存在するJavaパッケージフォルダ/src/main/java/io/quarkusに、 Particle.java という名前の新しいBeanクラスを以下の内容で作成します。

package io.quarkus;

public class Particle {

    private String name;

    public void setName(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return this.name;
    }

}

ここで、 ParticleResource.java という新しいRESTリソースクラスを作成し、いくつかのテストパーティクルを返します(ここでは原子物理学的な意味合いは無視します)。

package io.quarkus;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Set;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/particles")
public class ParticleResource {

    @GET
    public Set<Particle> getUsers() {

        Set <Particle> particleList = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>()));

        Particle particle = new Particle();
        particle.setName("Graviton");
        particleList.add(particle);

        Particle particle2 = new Particle();
        particle2.setName("Pentaquark");
        particleList.add(particle2);
        return particleList;
    }
}

開発モードでQuarkusを起動して、すべてが動作するかどうかをテストします。

$ mvn quarkus:dev

新しい端末で、エンドポイントを呼び出して、パーティクルの応答を確認します。

$ curl http://localhost:8080/particles

[{"name":"Graviton"},{"name":"Pentaquark"}]

Quarkusのデータレイヤーの準備ができたので、次はReactのGUIを作りましょう。

ステップ3 - Reactプロジェクトの生成

お使いのシステムに Node.jsnpm がインストールされていることを確認してください。必要に応じて、 公式ドキュメントを参照してください。

プロジェクト内に新しいフォルダ/src/main/ webapp を作成し、これをReactプロジェクトのベースフォルダとします。このフォルダで別のターミナルを開き、Reactプロジェクトイニシャライザを実行して、Patternfly Nodeモジュールを追加します。

$ npx create-react-app .
$ npm install @patternfly/patternfly --save
When starting the React app it will run on port 3000 by default. When we then try to access our Quarkus endpoint on port 8080 the browser will prevent this due to CORS security policies. In production this will not matter as the compiled static React app will be served from Quarkus over port 8080 as well but during development this is an issue.

幸いなことに、Reactには未知のエンドポイントへのすべてのリクエストを別のURLに転送する proxy というちょっとした機能があります。 webapp フォルダで package.json ファイルを開き、proxyの行を追加して、すべてのリクエストをQuarkusのポート8080に転送します。

{
  "name": "webapp",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.3",
    "web-vitals": "^1.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "proxy": "http://localhost:8080/",
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

ステップ4 - Patternflyのテーブルコンポーネントを作成して、データで埋める

Reactプロジェクトの /webapp/index.js にPatternflyのCSSをインポートします。

...
import './index.css';
import '@patternfly/patternfly/patternfly.css';
import App from './App';
...

webapp/srcに components という新しいフォルダを作り、その中に particles.js というファイルを作り、パーティクルオブジェクトのリストをループさせてPatternflyのテーブルを生成します。

import React from 'react'

const Particles = ({ particles }) => {
    return (
        <div>
            <center><h1>Particles List</h1></center>
            <table class="pf-c-table pf-m-grid-md" role="grid" aria-label="Supersonic Subatomic Particles" id="table-basic">
                <caption>Supersonic Subatomic Particles</caption>
                <thead>
                    <tr role="row">
                        <th role="columnheader" scope="col">Name</th>
                    </tr>
                </thead>
                {particles.map((particle) => (
                    <tbody role="rowgroup">
                        <tr role="row">
                            <td role="cell" data-label="Particle name">{particle.name}</td>
                        </tr>
                    </tbody>
                ))}
            </table>
        </div>

    )
};

export default Particles

次に、 /webapp/src/App.js を調整して、Quarkusサービスのエンドポイントを呼び出し、レスポンスデータを使ってパーティクルコンポーネントをレンダリングします。

import React, {Component} from 'react';
import Particles from './components/particles'

class App extends Component {
  state = {
    particles: []
  }

  componentDidMount() {
    fetch('/particles')
    .then(res => res.json())
    .then((data) => {
      this.setState({ particles: data })
    })
    .catch(console.log)
  }

  render () {
    return (
      <Particles particles={this.state.particles} />
    );
  }
}

export default App;

これで、Reactの開発モードでGUIをテストする準備が整いました。

別のターミナルでQuarkusがまだ起動していることを確認します。

/webapp フォルダ内で実行します。

$ npm start

ブラウザで http://localhost:3000 を開きます。開かない場合は、そのURLに移動してください。Quarkusのパーティクルバックエンドからテーブルが動的に入力されているのがわかると思います。ブラウザの開発者ツールでは、データが実際に http://localhost:3000/particles から取得されていることがわかります。

patternfly table

現在、ダブルデベロッパーモードで運用しています。JavaとJavascriptのどちらかのコードに加えられた変更は、すぐに適用されます。これは開発者にとって至福の時ではないでしょうか。

しかし、コンテナを使ったデプロイについてはどうでしょうか?新しいGUIアプリが完成したら、実行可能なjarやイメージを作成するために、いくつかの調整が必要になります。

ステップ5 - プロジェクトの本番ビルドへの準備

webapp/package.json にprodのビルドフェーズを追加:

...
 "eject": "react-scripts eject",
 "prod": "react-scripts build --dest && rsync -a build/* ../resources/META-INF/resources"
  },
  "proxy": "http://localhost:8080/",
...

これにより、静的なReactアセットのプロダクションビルドが行われ、Quarkusのデフォルトの静的ファイル用ディレクトリにコピーされます。

The rsync command is specific for Linux. For other operating systems please substitute with an equivalent command.

最後のステップでは、優れた frontend-maven-pluginを pom.xml に追加して、通常の maven ビルドから React のビルドフェーズを起動します。npmとnodeのバージョンを、ローカルにインストールされているものに合わせて調整します。mavenのインストールフェーズでは、実際にこれらの2つのパッケージがインストールされますが、これはCI/CDビルドやこのプロジェクトの起動時に便利です。

<build>
...
 <plugin>
  <groupId>com.github.eirslett</groupId>
  <artifactId>frontend-maven-plugin</artifactId>
  <version>1.11.2</version>
  <configuration>
    <workingDirectory>${project.basedir}/src/main/webapp</workingDirectory>
    <installDirectory>target</installDirectory>
  </configuration>
  <executions>
    <execution>
      <id>install node and npm</id>
      <goals>
        <goal>install-node-and-npm</goal>
      </goals>
      <configuration>
        <nodeVersion>v14.15.4</nodeVersion>
        <npmVersion>6.14.10</npmVersion>
      </configuration>
    </execution>
    <execution>
      <id>npm install</id>
      <goals>
        <goal>npm</goal>
      </goals>
      <configuration>
        <arguments>install</arguments>
      </configuration>
    </execution>
    <execution>
      <id>npm run build</id>
      <goals>
        <goal>npm</goal>
      </goals>
      <configuration>
        <arguments>run prod</arguments>
      </configuration>
    </execution>
  </executions>
</plugin>
</build>

それでは、実行可能なjarをビルドしてみましょう。

$ mvn clean package

他の稼働中の環境をすべて停止してポートを空け、実行します。

$ java -jar target/quarkus-app/quarkus-run.jar

ブラウザで http://localhost:8080 を開くと、最適化された単一のQuarkus jarから実行されているテーブルが表示されます。

まとめ

いくつかの簡単なステップで、ReactとPatternflyの力を使って、Quarkusアプリに洗練された見た目のインタラクティブなGUIを追加することができます。

このアプリを次は ネイティブにコンパイルして、極めて軽量なWebアプリを展開するのはどうでしょうか。あるいは、他の Patternflyコンポーネントを使用して、より洗練されたウェブインターフェイスを作成することもできます。いずれにしても、Quarkusアプリを輝かせるためには、あなたの創造性以外に制限はありません。