Java Quarkus Webサービスで Hello World する

# Java Quarkus Webサービスで Hello World する

こんにちは、@studio_meowtoon です。今回は、WSL の Ubuntu 22.04 で Java Quarkus Web アプリケーションを作成して Hello World を出力する方法を紹介します。
![quarkus_on_ubuntu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/4d181350-c5da-a587-d9b2-710fe246f138.png)

## 目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

https://qiita.com/studio_meowtoon/items/6119ab05d8b13527efd8

## 実現すること

ローカル環境の Ubuntu で、Java Quarkus Web サービスの JAR ファイル形式、また、ネイティブイメージ形式のアプリを起動します。

**① JAR ファイル形式のアプリを起動**
```mermaid
flowchart LR
    terminal --> app[app-runner.jar]
    subgraph Ubuntu
        subgraph JVM
            subgraph app[app-runner.jar]
                netty
            end
        end
    end
style Ubuntu fill:#FFBCA4, stroke:#E0460F
style JVM fill:#DCFFDC, stroke:#2F872F
style app fill:#ECECFF, stroke:#9674DC
style netty fill:#FFFFDE, stroke:#B4AE43
```

|要素|概要|
|:--|:--|
|terminal|ターミナル|
|Ubuntu|OS|
|JVM|Java 実行環境|
|app-runner.jar|Java アプリケーション|
|netty|Web サーバー|

**② ネイティブイメージ形式のアプリを起動**
```mermaid
flowchart LR
    terminal --> app[app-runner]
    subgraph Ubuntu
        subgraph app[app-runner]
            netty
        end
    end
style Ubuntu fill:#FFBCA4, stroke:#E0460F
style app fill:#ECECFF, stroke:#9674DC
style netty fill:#FFFFDE, stroke:#B4AE43
```

|要素|概要|
|:--|:--|
|terminal|ターミナル|
|Ubuntu|OS|
|app-runner|ネイティブイメージ アプリケーション|
|netty|Web サーバー|

## 技術トピック

### Quarkus とは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

:::note info

### [Quarkus (クォーカス)](https://ja.quarkus.io/about/)

Quarkus は、Java アプリケーションを高速で軽量なマイクロサービスやクラウドネイティブアプリケーションとして実行するためのフレームワークです。

|キーワード|内容|
|:--|:--|
|オープンソース|Quarkus はオープンソースであり、活発なコミュニティによってサポートされています。|
|クラウドネイティブ|Kubernetes などのコンテナ環境との統合が簡単であり、クラウドネイティブアプリケーションの開発を容易にします。|
|マイクロサービス|小規模で疎結合なマイクロサービスアーキテクチャの開発に適しています。|
|軽量で高速|Quarkus は GraalVM によるネイティブイメージ生成をサポートし、高速で起動時間が短いアプリケーションを実現します。|
|低メモリ使用量|小さなコンテナサイズや省エネルギーな環境に適しています。|
:::

</details>

### Java におけるネイティブイメージビルドとは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

:::note info
|キーワード|内容|
|:--|:--|
|ネイティブイメージビルド|Java コードをネイティブマシンコードにコンパイルすることです。通常、Java コードは Java バイトコードと呼ばれる中間言語にコンパイルされ、Java 仮想マシン (JVM) で実行されます。しかし、**ネイティブイメージビルド**は、Java コードを **JVM を介さず**に直接実行可能な**ネイティブマシンコードに変換**することで、**より高速な実行速度**と**より低いメモリ使用量**を実現することができます。|

ネイティブイメージビルドは、以下のようなニーズから求められています。  

|キーワード|内容|
|:--|:--|
|パフォーマンスの向上|Java は一般的に高水準のプログラミング言語であり、JVM によって実行されるため、実行速度が遅いとされることがあります。ネイティブイメージビルドにより、高速な実行速度を実現することができます。|
|メモリの最適化|JVM による Java コードの実行には、多くのメモリが必要となることがあります。ネイティブイメージビルドにより、より少ないメモリ使用量でプログラムを実行できるようになります。|
|ネイティブプログラムの統合|Java は、C言語や C++ などの他のプログラミング言語で書かれたネイティブプログラムと統合することができます。しかし、統合するためには、ネイティブコードが必要になります。ネイティブイメージビルドにより、これらのネイティブプログラムと Java コードをシームレスに統合することができます。|
:::

</details>

## 開発環境

+ Windows 11 Home 22H2 を使用しています。  

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作していきますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
> wsl --version
WSL バージョン: 1.0.3.0
カーネル バージョン: 5.15.79.1
WSLg バージョン: 1.0.47
```

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
```

Java JDK [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/4d11e94a2389758759cd)

```sh
$ java -version
openjdk version "11.0.18" 2023-01-17
OpenJDK Runtime Environment (build 11.0.18+10-post-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.18+10-post-Ubuntu-0ubuntu122.04, mixed mode, sharing)
```

Maven [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/f4cdc4039158153365fb)

```sh
$ mvn -version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 11.0.18, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
```

</details>

:::note warn
この記事では基本的に Ubuntu のターミナルで操作を行います。**Vim を使用してコピペする方法**を初めて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
:::

https://qiita.com/studio_meowtoon/items/7771ad48676926e41463

## 作成する Web アプリケーションの仕様

|No|エンドポイント|HTTPメソッド|MIME タイプ|
|:--|:--|:--|:--|
|1|/api/data|GET|application/json|

<details>
<summary>説明を開きます。</summary>

:::note info
**/api/data** という**エンドポイント**に対して **HTTP GET** リクエストを送信すると、**JSON データ**がレスポンスされるシンプルな Web サービスを実装します。
:::

</details>

## Hello World を表示する手順

### プロジェクトの作成

プロジェクトフォルダを作成します。  
※ ~/tmp/hello-quarkus をプロジェクトフォルダとします。

```sh
$ mkdir -p ~/tmp/hello-quarkus
$ cd ~/tmp/hello-quarkus
```

### リソースクラスの作成

リソースクラスを作成します。

```sh
$ mkdir -p src/main/java/com/example/quarkus
$ vim src/main/java/com/example/quarkus/HelloResource.java
```

ファイルの内容

```java:HelloResource.java
package com.example.quarkus;

import java.util.Map;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/api")
public class HelloResource {

    @GET
    @Path("/data")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, String> getData() {
        Map<String, String> map = Map.of("message", "Hello World!");
        return map;
    }
}
```

### pom.xml の作成

pom.xml ファイルを作成します。

```sh
$ vim pom.xml
```

ファイルの内容  

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>hello-quarkus</artifactId>
    <version>1.0</version>
    <name>hello-quarkus</name>

    <properties>
        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
        <quarkus.platform.version>2.16.8.Final</quarkus.platform.version>
        <compiler-plugin.version>3.8.1</compiler-plugin.version>
        <java.version>11</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>${quarkus.platform.artifact-id}</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <finalName>app</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${compiler-plugin.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>quarkus-maven-plugin</artifactId>
                <version>${quarkus.platform.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>build</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
```

### ディレクトリ・ファイル構成

プロジェクトのファイル構成を表示してみます。

```sh
$ tree
.
├── pom.xml
└── src
    └── main
        └── java
            └── com
                └── example
                    └── quarkus
                        └── HelloResource.java
```

### 開発モードでアプリを起動

開発モードでアプリを起動します。  
※ アプリを停止するときは ctrl + C を押します。

```sh
$ mvn quarkus:dev
```

出力

```sh
Listening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2023-07-31 15:35:11,682 INFO  [io.quarkus] (Quarkus Main Thread) hello-quarkus 1.0 on JVM (powered by Quarkus 2.16.8.Final) started in 1.451s. Listening on: http://0.0.0.0:8080

2023-07-31 15:35:11,687 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2023-07-31 15:35:11,688 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, vertx]

--
Tests paused
Press [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>
```

:::note info
ここまでの手順で、Quarkus アプリを開発モードで起動することができました。
:::

### アプリの動作確認

別ターミナルから curl コマンドで確認します。

```sh
$ curl -v http://localhost:8080/api/data -w '\n'
```

出力

```
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api/data HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-length: 22
< Content-Type: application/json;charset=UTF-8
<
* Connection #0 to host localhost left intact
{message=Hello World!}
```

:::note info
ここまでの手順で、ターミナルに **{"message":"Hello World!"}** と表示され、**JSON データ**を取得することが出来ました。 
::: 

## jar ファイルのビルド

### アプリのビルド

Java アプリをビルドします。
※ target/app-runner.jar が作成されます。  

```sh
$ mvn clean package \
    -Dquarkus.package.type=uber-jar
```

実行します。

```sh
$ java -jar target/app-runner.jar
```

:::note info
ここまでの手順で、Quarkus アプリの **JAR ファイル**をビルド・起動することができました。
:::

## ネイティブイメージをビルド

### pom.xml の修正

pom.xml ファイルを修正します。

```sh
$ vim pom.xml
```

<details>
<summary>ファイルの内容全体を表示する</summary>

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>hello-quarkus</artifactId>
    <version>1.0</version>
    <name>hello-quarkus</name>

    <properties>
        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
        <quarkus.platform.version>2.16.8.Final</quarkus.platform.version>
        <compiler-plugin.version>3.8.1</compiler-plugin.version>
        <surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
        <java.version>11</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>${quarkus.platform.artifact-id}</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <finalName>app</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${compiler-plugin.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>quarkus-maven-plugin</artifactId>
                <version>${quarkus.platform.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>build</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <profiles>
        <profile>
            <id>native</id>
            <activation>
                <property>
                    <name>native</name>
                </property>
            </activation>
            <properties>
                <quarkus.package.type>native</quarkus.package.type>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <artifactId>maven-failsafe-plugin</artifactId>
                        <version>${surefire-plugin.version}</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>integration-test</goal>
                                    <goal>verify</goal>
                                </goals>
                                <configuration>
                                    <systemPropertyVariables>
                                        <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
                                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                                        <maven.home>${maven.home}</maven.home>
                                    </systemPropertyVariables>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>
```

</details>

ファイルの内容 ※一部抜粋

```xml:pom.xml ※一部抜粋
</project>
    <!--省略-->
    <properties>
        <!--省略-->
        <surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
        <!--省略-->
    </properties>
    <!--省略-->
    <profiles>
        <profile>
            <id>native</id>
            <activation>
                <property>
                    <name>native</name>
                </property>
            </activation>
            <properties>
                <quarkus.package.type>native</quarkus.package.type>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <artifactId>maven-failsafe-plugin</artifactId>
                        <version>${surefire-plugin.version}</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>integration-test</goal>
                                    <goal>verify</goal>
                                </goals>
                                <configuration>
                                    <systemPropertyVariables>
                                        <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
                                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                                        <maven.home>${maven.home}</maven.home>
                                    </systemPropertyVariables>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>
```

Docker デーモンを起動します。

```sh
$ sudo service docker start
 * Starting Docker: docker    [ OK ]
```

:::note warn
Docker 環境をお持ちでない場合は、以下の関連記事から Docker Engine のインストール手順をご確認いただけます。
:::

https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe

:::note warn
**GraalVM** はビルド時に Docker コンテナとして提供されます。  
:::

ネイティブイメージをビルドします。
※ target/app-runner ネイティブイメージが作成されます。

```sh
$ mvn clean package -Pnative
```

実行します。

```sh
$ ./target/app-runner
```

:::note info
ここまでの手順で、Quarkus アプリの **ネイティブイメージ**をビルド・起動することができました。
:::

## アプリ起動時間の比較

|ファイル形式|実行ファイル|起動時間(秒)|
|:--|:--|:--|
|JAR ファイル形式|app-runner.jar|0.642秒|
|ネイティブイメージ形式|app-runner|0.017秒|

:::note info
JVM で動作する Quarkus アプリの起動時間も十分速いですが、**ネイティブイメージ**だと**起動時間が非常に速い**とわかりました。
:::

## まとめ

Ubuntu に構築したシンプルな Java 開発環境で、Java Quarkus Web サービスを実行することができました。

:::note warn
実際の業務での Quarkus の使用ケースでは、構成ファイルなどが追加されるため、ここで示したようなシンプルな構成とは異なる場合があります。しかしながら、最小構成の例を学ぶことで、Quarkus がどのような構成が必要なのかを理解することができます。
:::

どうでしたか？ WSL Ubuntu で、Java Quarkus Web アプリケーション開発環境を手軽に構築することができます。ぜひお試しください。今後も Java の開発環境などを紹介していきますので、ぜひお楽しみにしてください。

## 推奨コンテンツ

https://qiita.com/studio_meowtoon/items/b7ab1619143669329498

## 関連記事

#### Java JAX-RS

https://qiita.com/studio_meowtoon/items/de843e5ce499825bd13d

#### Java Spring Boot

https://qiita.com/studio_meowtoon/items/37ac0082a3228e4de570