Spring Cache APIのためのQuarkusエクステンション
ユーザーは キャッシュのQuarkusアノテーションを使用することが推奨されていますが、Quarkusは、 spring-cache
エクステンションの形式でSpring Cacheアノテーションのための互換性レイヤーを提供しています。
このガイドでは、Quarkusアプリケーションが、よく知られているSpring Cacheアノテーションを活用して、Spring Beansのアプリケーションデータのキャッシングを可能にする方法を説明します。
前提条件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 17+がインストールされ、
JAVA_HOME
が適切に設定されていること -
Apache Maven 3.9.9
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
-
Spring DI エクステンションにある程度慣れている
Mavenプロジェクトの作成
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。
Windowsユーザーの場合:
-
cmdを使用する場合、(バックスラッシュ
\
を使用せず、すべてを同じ行に書かないでください)。 -
Powershellを使用する場合は、
-D
パラメータを二重引用符で囲んでください。例:"-DprojectArtifactId=spring-cache-quickstart"
このコマンドは、 spring-cache
と spring-di
のエクステンションをインポートするプロジェクトを生成します。
すでにQuarkusプロジェクトが設定されている場合は、プロジェクトのベースディレクトリーで以下のコマンドを実行することで、プロジェクトに spring-cache
エクステンションを追加することができます。
quarkus extension add spring-cache
./mvnw quarkus:add-extension -Dextensions='spring-cache'
./gradlew addExtension --extensions='spring-cache'
これにより、ビルドファイルに以下が追加されます。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-cache</artifactId>
</dependency>
implementation("io.quarkus:quarkus-spring-cache")
REST APIの作成
まずは、外部の気象サービスへの非常に遅い呼び出しをシミュレートするサービスを作成してみましょう。以下の内容で src/main/java/org/acme/spring/cache/WeatherForecastService.java
を作成します。
package org.acme.spring.cache;
import java.time.LocalDate;
import org.springframework.stereotype.Component;
@Component
public class WeatherForecastService {
public String getDailyForecast(LocalDate date, String city) {
try {
Thread.sleep(2000L); (1)
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return date.getDayOfWeek() + " will be " + getDailyResult(date.getDayOfMonth() % 4) + " in " + city;
}
private String getDailyResult(int dayOfMonthModuloFour) {
switch (dayOfMonthModuloFour) {
case 0:
return "sunny";
case 1:
return "cloudy";
case 2:
return "chilly";
case 3:
return "rainy";
default:
throw new IllegalArgumentException();
}
}
}
1 | 遅さの原因はここにあります。 |
また、ユーザーが3日間の天気予報を聞いてきたときに、ユーザーに送信されるレスポンスを含むクラスも必要です。 src/main/java/org/acme/spring/cache/WeatherForecast.java
をこのように作成します:
package org.acme.spring.cache;
import java.util.List;
public class WeatherForecast {
private List<String> dailyForecasts;
private long executionTimeInMs;
public WeatherForecast(List<String> dailyForecasts, long executionTimeInMs) {
this.dailyForecasts = dailyForecasts;
this.executionTimeInMs = executionTimeInMs;
}
public List<String> getDailyForecasts() {
return dailyForecasts;
}
public long getExecutionTimeInMs() {
return executionTimeInMs;
}
}
あとは、サービスとレスポンスを使用するために WeatherForecastResource
クラスを作成する必要があります。
package org.acme.spring.cache;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.jboss.resteasy.reactive.RestQuery;
@Path("/weather")
public class WeatherForecastResource {
@Inject
WeatherForecastService service;
@GET
public WeatherForecast getForecast(@RestQuery String city, @RestQuery long daysInFuture) { (1)
long executionStart = System.currentTimeMillis();
List<String> dailyForecasts = Arrays.asList(
service.getDailyForecast(LocalDate.now().plusDays(daysInFuture), city),
service.getDailyForecast(LocalDate.now().plusDays(daysInFuture + 1L), city),
service.getDailyForecast(LocalDate.now().plusDays(daysInFuture + 2L), city)
);
long executionEnd = System.currentTimeMillis();
return new WeatherForecast(dailyForecasts, executionEnd - executionStart);
}
}
1 | daysInFuture クエリパラメーターが省略された場合、3 日間の天気予報は現在の日から始まります。それ以外の場合は、現在の日に daysInFuture の値を加えたものから始まります。 |
全て終わりました!正常に動作しているか確認してみましょう。
まず、アプリケーションを実行します。
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
次に、ブラウザから http://localhost:8080/weather?city=Raleigh
を呼び出します。6秒ほど長い時間が経過すると、アプリケーションはこのような回答をします:
{"dailyForecasts":["MONDAY will be cloudy in Raleigh","TUESDAY will be chilly in Raleigh","WEDNESDAY will be rainy in Raleigh"],"executionTimeInMs":6001}
コードを実行する日によってレスポンスの内容が異なる場合があります。 |
何度同じURLを呼び出してみても、常に6秒で返事が返ってきます。
キャッシュの有効化
Quarkusアプリケーションが稼働しているので、外部の気象サービスのレスポンスをキャッシュすることで、レスポンスタイムを大幅に改善してみましょう。 WeatherForecastService
クラスを以下のように更新します:
package org.acme.cache;
import java.time.LocalDate;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
@Component
public class WeatherForecastService {
@Cacheable("weather-cache") (1)
public String getDailyForecast(LocalDate date, String city) {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return date.getDayOfWeek() + " will be " + getDailyResult(date.getDayOfMonth() % 4) + " in " + city;
}
private String getDailyResult(int dayOfMonthModuloFour) {
switch (dayOfMonthModuloFour) {
case 0:
return "sunny";
case 1:
return "cloudy";
case 2:
return "chilly";
case 3:
return "rainy";
default:
throw new IllegalArgumentException();
}
}
}
1 | このアノテーション(もちろん関連するインポートも)を追加しただけです。 |
http://localhost:8080/weather?city=Raleigh
をもう一度呼び出して確認してみてください。返事が来るまでにまだ長い時間待たされています。これはサーバーが再起動したばかりでキャッシュが空になっているので正常です。
ちょっと待って!? WeatherForecastService
のアップデート後、サーバーが勝手に再起動した?はい、これは、 live coding
と呼ばれる開発者のためのQuarkusの驚くべき機能の一つです。
前回の呼び出しでキャッシュが読み込まれたので、同じ URL を呼び出してみてください。今度は、 executionTimeInMs
の値が 0 に近い超高速な応答が返ってくるはずです。
URL http://localhost:8080/weather?city=Raleigh&daysInFuture=1
を使って未来のある日から始めるとどうなるか見てみましょう。要求された日のうち2つはすでにキャッシュに読み込まれていたので、2秒後に回答が得られるはずです。
また、同じURLを別の都市で呼び出してみて、再度キャッシュの動作を確認することもできます。最初の呼び出しには6秒ほどかかり、次の呼び出しにはすぐに出ます。
おめでとうございます!たった1行のコードでQuarkusアプリケーションにアプリケーションデータのキャッシングを追加できました!
サポートされている機能
Quarkusは、以下のSpring Cacheアノテーションとの互換性を提供しています。
-
@Cacheable
-
@CachePut
-
@CacheEvict
この最初のバージョンの Spring Cache アノテーションエクステンションでは、これらのアノテーションのすべての機能がサポートされているわけではないことに注意してください (サポートされていない機能を使用しようとすると、適切なエラーがログに記録されます)。しかし、今後のリリースでは追加機能が計画されています。