backend-example-quarkusを実装する過程での知見を整理した。QuarkusでReactiveなRestful APIを実装する際の勘所をまとめている。
基礎知識を学ぶ
RESTful APIを実装するのであれば、下記が全体を理解するのに役に立つ。
開発環境を構築する
Quarkus CLIというスタンドアロンなコマンドが提供されているが、Mavenプラグインで同じことができるため、こちらの方が手軽。Quarkus CLIを利用する場合、WindowsであればScoopでインストールするのが簡単である。
IDEにVisual Studio Codeを利用してDevContainerで開発環境を作る方法がおすすめ。devcontainer.json設定例では、Java Featureに加えてQuarkusプラグインとSonarLintプラグインを設定している。
プロジェクトを新規作成する
code.quarkus.ioで作成したプロジェクトをインポートするか、Quarkus CLIもしくはMavenプラグインでプロジェクト作成する。
Quarkus CLIでのプロジェクト作成
quarkus create app {Group ID}:{Artifact ID} --extension=resteasy-reactive-jackson --no-code
基本的な開発の流れ
Visual Studio Codeでソースコードを編集して、Maven (Wrapper) でアプリケーションを開発モードで起動・テスト実行、必要に応じてVisual Studio Codeのデバッガを接続する、という流れが効率がよい。
# 開発モードでアプリケーションを起動する
./mvnw quarkus:dev
デバッガの接続のための設定はlaunch.jsonを参照。Visual Studio Codeの「実行とデバッグ」メニューからデバッガを起動する他に、QuarkusプラグインでCtrl + Shift + P
→ Quarkus: Debug current Quarkus project
を実行すると、アプリ起動+デバッグ実行を一発でできる。(参考:Quarkus developer joy for VS Code)
ファイル更新時にSonarLintの分析が動作して問題パネルに指摘が表示される。現在のところ、全ファイルを一括で分析することはできないとのこと。(参考:How to enable SonarLint on all files in Visual Studio code - SonarLint / VS Code - Sonar Community)
入力に対するバリデーションを行う
Hibernate Validator (Bean Validation) がサポートされている。Extenstionをインストールすれば使えるようになる。
quarkus extension add 'hibernate-validator'
参考:VALIDATION WITH HIBERNATE VALIDATOR
Hibernate ValidatorビルトインのアノテーションはHibernate Validatorのドキュメントを参照。独自バリデーション(アノテーション)の実装方法は6. Creating custom constraintsを参照。
共通の例外処理を実装する
JAX-RSのExceptionMapperとRESTEasyのServerExceptionMapperの両方がサポートされている。(参考:Exception Mapping)両者の機能的な差異は、ServerExceptionMapperでは複数のExceptionを受け取る設定をアノテーションで記載する機能がある程度。
ビルトインのExceptionMapperが定義されており、エラーハンドリングをカスタマイズするにはPriorityを設定して上書き登録が必要となる。また、これが原因で全ての例外を受け取るExceptionMapper定義もできない制約がある。(参考:built-in ExceptionMappers cause unexpected behavior · Issue #7883 · quarkusio/quarkus)
Quarkus Securityにて発生する例外をExceptionMapperで扱うには以下の設定を入れる必要がある。
quarkus.http.auth.proactive=false
ただし、smallrye-jwt (quarkus-smallrye-jwt) のトークン検証エラー(AuthenticationFailedException)を受け取るExceptionMapperを定義しようとすると java.lang.IllegalStateException: Response has already been written
が発生することを確認している。原因は調査できていない。
データベースにアクセスする
ORM (+α) を使いたい場合はHibernate Reactive + Panache (quarkus-hibernate-reactive-panache) を利用するとよい。(2022年9月時点ではPreview扱い。)
実装例:TodoEntity.java
実装上のTips
-
レコードのIDをカスタマイズする場合はPanacheEntityではなくPanacheEntityBaseを使う。(参考:Custom IDs)
-
ページングはpageメソッドで実装できる。(参考:Paging)
MyEntity.findAll().page(Page.of(...))
-
テーブルにインデックスを設定するには
@Table
アノテーションのindexes
プロパティを設定すればよい。(参考:Defining Indexes in JPA | Baeldung) -
アプリケーション起動時にデータベースの初期化を実行したい場合はStartupEventを捕捉するメソッドを用意すればよい。(参考:DBInit.java)
@ApplicationScoped public class DBInit { void onStart(@Observes StartupEvent ev) { ... }
参考
- SIMPLIFIED HIBERNATE REACTIVE WITH PANACHE
- hibernate-reactive-panache-quickstart
- QuarkusのHibernate Reactive with Panache Extensionを試してみようとした話
トランザクションを利用する
Bean(コンポーネント)のメソッドに@ReactiveTransactional
を記述することで、トランザクション境界を設定できる。もしくはPanache.withTransaction()
の中でDB操作を記述することで手続き的に記述もできる。
参考:Simplified Hibernate Reactive with Panache - Quarkus
トランザクションを使っている場合、複数のクエリを Uni.combine
で結合すると以下のエラーが発生するため、withTransaction
で囲う記述とする必要がある。(参考:First Request to Reactive SQL fail · Issue #14709 · quarkusio/quarkus)
java.lang.IllegalStateException: session is currently connecting to database
アクセストークンを利用した認証を実装する
以下の3つのExtensionが選択肢となる。
- quarkus-oidc
- quarkus-smallrye-jwt
- quarkus-elytron-security-oauth2
今回のプロジェクトではJWT形式のトークンの検証ができればよいのでquarkus-smallrye-jwtを選択した。Quarkus CLIでインストールできる。
quarkus extension add 'smallrye-jwt'
javax.annotation.securityやJAX-RS (javax.ws.rs.core) のアノテーションの他にQuarkus独自 (io.quarkus.security) のアノテーションを使って権限設定ができる。(参考:Authorization using Annotations)
実装上のTips
-
デフォルトではJWTトークンにiatクレームが指定されていることが必須となるが、下記の設定で任意に変更できる。
smallrye.jwt.time-to-live=-1
インターセプタを実装する
Java Interceptors (javax.interceptor) がサポートされているため、@Interceptorなどのアノテーションを利用できる。(参考:Interceptors)
ログを出力する
JDK (JUL), JBoss Logging, SLF4J, Apache Commons LoggingのAPIがサポートされている。(内部でJBoss Loggingに橋渡しされる仕組み。)
参考:Configuring Logging - Quarkus
LoggerインスタンスをDIする方法の他に、staticメソッドで呼び出す仕組み(コンパイル時にインスタンスを利用するコードに置き換えられる)も提供される。
package com.example;
import io.quarkus.logging.Log;
class MyService {
public void doSomething() {
Log.info("Simple!");
}
}
ログ設定はapplication.propertiesに記載する。(参考:Runtime configuration)
単体テストを実装する
quarkus-junit5を依存関係に追加してテストクラスに@QuarkusTestを設定すると、JUnit5を利用したテスト実装にて、アプリケーションのテスト用の起動やテスト対象クラスのDIなどの便利機能が利用できるようになる。(参考:TESTING YOUR APPLICATION)
Uni/MultiのアサーションにはSubscriberを利用する。(参考:How can I write unit / integration tests? - SmallRye Mutiny)
Mockitoの基本的なサポートはquarkus-junit5に含まれているが、quarkus-junit5-mockitoを追加すると@InjectMockなど便利アノテーションが使えるようになる。(参考:Mock Support)
Panacheをモック化するにはquarkus-panache-mockを使う。
- PanacheQueryインスタンスを返すEntityメソッドをモック化する場合はPanacheQueryのモックオブジェクトを用意する。(詳細は公式ガイドを参照。)
- PanacheQueryのモック生成時はMockitoの制約により、キャストが必要になることに注意。(参考:Using Mockito to mock classes with generic parameters)
Quarkus Securityをモック化するにはquarkus-test-securityを使う。(参考:Security Testing)
- 単体テストのコンテキストでJAX-RSのSecurityContextを参照しようとしたタイミングで
java.lang.IllegalStateException: No RESTEasy Reactive request in progress
が発生するため、Mockitoでモック化する必要がある。(原因不明)
単体テストの実行はVisual Studio Codeのテストツールから行ってもよいが、起動に時間がかかるため、Quarkusのdev modeを起動(mvn quarkus:dev
)しておき、テスト再実行(r
)すると効率がよい。
E2Eテストを実装する
E2Eテストの実装にはRESTAssuredを利用できる。@TestHttpEndpointをクラスもしくはメソッドに指定するとRestassuredのbasePathをリソースのパスに自動で設定してくれる。(参考:5.2 Restassured)
アプリケーションの設定を管理する
アプリケーション設定はシステムプロパティ、環境変数、.envファイル、プロパティファイル等で入れ込める。ソースの優先順位はConfig Sourcesを参照。
起動ポートなどの動作設定など設定可能な項目はConfiguring Quarkusを参照。独自のアプリケーション設定を定義することもできる。
環境(の分類)ごとの設定をプロファイルとして管理できる。デフォルトでは dev
test
prod
の3つのプロファイルが定義される。(参考:4. Profiles)
プロパティのキーのプレフィックスを %{profile-name}.
の形式にすることで、特定のプロファイル用の設定を定義できる。もしくは特定のプロファイル用のプロパティファイルを application-{profole-name}.properties
として作成できる。
プログラム中でプロファイル名を取得するには ProfileManager.getActiveProfile()
を呼び出せばよい。(参考:How to determine programmatically the current active profiles in Quarkus - Stack Overflow)
Nativeイメージをビルドする
自力でGraalVMをインストールしてビルドすることもできるがDockerコンテナを使うのが簡単。Quarkus CLI (Mavenプラグイン) ではさらにコンテナを自動で起動してビルドしてくれるコマンドが提供されている。
./mvnw package -Pnative -Dquarkus.native.container-build=true
参考:Creating Linux executable without GraalVM installed
Nativeイメージが ./target/{project name and version}-runner
に作成されるので、実行すればOK。