概要
最近QuarkusというJavaフレームワークの存在を知りました。
Quarkus は、Java 仮想マシン (JVM) およびネイティブコンパイルのために作成されたフルスタックの Kubernetes ネイティブ Java フレームワークで、Java をコンテナに最適化し、サーバーレス、クラウド、Kubernetes の各環境で効果的なプラットフォームとして使用できるようにします。
https://www.redhat.com/ja/topics/cloud-native-apps/what-is-quarkus
だそうです。
KubernetesネイティブなJavaフレームワークということで、マイクロサービスが流行りつつある今、個人的には将来性のあるフレームワークなのかな、と思っています。
特徴や詳しい使い方は他の方に任せます。(
こちらの記事では、Quarkusのサイトで紹介されているredis-clientを使ったサンプルアプリケーションの作り方をまとめました。
Redisと接続してCRUDを行うRESTful APIアプリケーションです。
↓のページに記載されている内容を、私の方でいくつか加(減)筆したものになります。
https://quarkus.io/guides/redis
手順
Maven Projectを作る
quickに新規でQuarkusプロジェクトを新規で作成するのであれば、以下のコマンドを実行するだけです。
-DprojectGroupId
や-DprojectArtifactId
はお好みで変更してください。
mvn io.quarkus:quarkus-maven-plugin:1.13.4.Final:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=redis-quickstart \
-Dextensions="redis-client,resteasy-jackson,resteasy-mutiny" \
-DnoExamples
cd redis-quickstart
※Quarkusのバージョン1.13.4時点では、3.6.2以降のバージョンのmavenが必要です。
-Dextensions
で、Quarkus+Redisなアプリケーションを実装するのに必要なライブラリが指定されています。
- redis-client: Redisのクライアントライブラリです。
- resteasy-jackson, resteasy-mutiny: REST APIを作るのに必要なライブラリです。
既に作成されているMavenプロジェクトにライブラリを追加する場合は、以下のコマンドを実行するか、
./mvnw quarkus:add-extension -Dextensions="redis-client"
pom.xmlに以下を追記してください。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client</artifactId>
</dependency>
Redisサーバを用意する
Redisサーバを用意されていない方は、DockerでRedisコンテナを用意しておきましょう。
docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name redis_quarkus_test -p 6379:6379 redis:5.0.6
アプリケーションの作成
ここからソースコードを触っていきます。
事前に下記コマンドで必要なディレクトリを作成しておきます。
# redis-quickstart配下で
mkdir -p src/{main,test}/java/org/acme/redis/
application.properties
application.propertiesにRedisサーバのURLを記述します。
quarkus.redis.hosts=redis://localhost:6379
POJO
ソースコードの解説は時間があるときにやります(やらないとは言っていない…言ってない!)
package org.acme.redis;
public class Increment {
public String key;
public int value;
public Increment(String key, int value) {
this.key = key;
this.value = value;
}
public Increment() {
}
}
Service
package org.acme.redis;
import io.quarkus.redis.client.RedisClient;
import io.quarkus.redis.client.reactive.ReactiveRedisClient;
import io.smallrye.mutiny.Uni;
import io.vertx.mutiny.redis.client.Response;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
class IncrementService {
@Inject
RedisClient redisClient;
@Inject
ReactiveRedisClient reactiveRedisClient;
Uni<Void> del(String key) {
return reactiveRedisClient.del(Arrays.asList(key))
.map(response -> null);
}
String get(String key) {
return redisClient.get(key).toString();
}
void set(String key, Integer value) {
redisClient.set(Arrays.asList(key, value.toString()));
}
void increment(String key, Integer incrementBy) {
redisClient.incrby(key, incrementBy.toString());
}
Uni<List<String>> keys() {
return reactiveRedisClient
.keys("*")
.map(response -> {
List<String> result = new ArrayList<>();
for (Response r : response) {
result.add(r.toString());
}
return result;
});
}
}
Resource
package org.acme.redis;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.PathParam;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.POST;
import javax.ws.rs.DELETE;
import java.util.List;
import io.smallrye.mutiny.Uni;
@Path("/increments")
public class IncrementResource {
@Inject
IncrementService service;
@GET
public Uni<List<String>> keys() {
return service.keys();
}
@POST
public Increment create(Increment increment) {
service.set(increment.key, increment.value);
return increment;
}
@GET
@Path("/{key}")
public Increment get(@PathParam("key") String key) {
return new Increment(key, Integer.valueOf(service.get(key)));
}
@PUT
@Path("/{key}")
public void increment(@PathParam("key") String key, Integer value) {
service.increment(key, value);
}
@DELETE
@Path("/{key}")
public Uni<Void> delete(@PathParam("key") String key) {
return service.del(key);
}
}
Test
こちらはオプショナルです。
package org.acme.redis;
import static org.hamcrest.Matchers.is;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
import static io.restassured.RestAssured.given;
import io.restassured.http.ContentType;
@QuarkusTest
public class IncrementResourceTest {
@Test
public void testRedisOperations() {
// verify that we have nothing
given()
.accept(ContentType.JSON)
.when()
.get("/increments")
.then()
.statusCode(200)
.body("size()", is(0));
// create a first increment key with an initial value of 0
given()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.body("{\"key\":\"first-key\",\"value\":0}")
.when()
.post("/increments")
.then()
.statusCode(200)
.body("key", is("first-key"))
.body("value", is(0));
// create a second increment key with an initial value of 10
given()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.body("{\"key\":\"second-key\",\"value\":10}")
.when()
.post("/increments")
.then()
.statusCode(200)
.body("key", is("second-key"))
.body("value", is(10));
// increment first key by 1
given()
.contentType(ContentType.JSON)
.body("1")
.when()
.put("/increments/first-key")
.then()
.statusCode(204);
// verify that key has been incremented
given()
.accept(ContentType.JSON)
.when()
.get("/increments/first-key")
.then()
.statusCode(200)
.body("key", is("first-key"))
.body("value", is(1));
// increment second key by 1000
given()
.contentType(ContentType.JSON)
.body("1000")
.when()
.put("/increments/second-key")
.then()
.statusCode(204);
// verify that key has been incremented
given()
.accept(ContentType.JSON)
.when()
.get("/increments/second-key")
.then()
.statusCode(200)
.body("key", is("second-key"))
.body("value", is(1010));
// verify that we have two keys in registered
given()
.accept(ContentType.JSON)
.when()
.get("/increments")
.then()
.statusCode(200)
.body("size()", is(2));
// delete first key
given()
.accept(ContentType.JSON)
.when()
.delete("/increments/first-key")
.then()
.statusCode(204);
// verify that we have one key left after deletion
given()
.accept(ContentType.JSON)
.when()
.get("/increments")
.then()
.statusCode(200)
.body("size()", is(1));
// delete second key
given()
.accept(ContentType.JSON)
.when()
.delete("/increments/second-key")
.then()
.statusCode(204);
// verify that there is no key left
given()
.accept(ContentType.JSON)
.when()
.get("/increments")
.then()
.statusCode(200)
.body("size()", is(0));
}
}
※手順に従って進めていましたが、私の場合はio.hamcrest
とio.restassured
のライブラリが見つからず、エラーとなりました。
エラーが発生した場合は、以下をpom.xml
に追加してください。
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>3.0.0</version>
<scope>test</scope>
<dependency>
ソースコードの編集は以上です。
アプリケーションを動かしてみる
下記コマンドでアプリケーションを動かしてみましょう。
./mvnw quarkus:dev
起動すると、下記のようなログがコンソールに表示されます。(1.3秒くらいで起動できてますね。)
作成したアプリケーションを試してみる
起動が完了すると8080ポートをListenするので、curlコマンドで/increments
にPOSTしてみましょう。
curl -X POST -H "Content-Type: application/json" -d '{"key":"first","value":10}' http://localhost:8080/increments
# -> {"key":"first","value":10}
/increments
をGETすると、登録したキーのリストが返ってきます。
curl http://localhost:8080/increments
# -> ["first"]
キーを指定してGETをすれば、key-valueがJsonで返却されます。
curl http://localhost:8080/increments/first
# -> {"key":"first","value":10}
念のため、Redisサーバにアクセスしてレコードを確認してみます。
docker exec -it redis_quarkus_test redis-cli
127.0.0.1:6379> keys *
# -> 1) "first"
127.0.0.1:6379> get first
# -> "10"
ちゃんと登録されてますね。
PUTをすると、指定したキーの値を指定した分だけ増加させます。
curl -X PUT -H "Content-Type: application/json" -d '27' http://localhost:8080/increments/first
curl http://localhost:8080/increments/first
# -> {"key":"first","value":37}
DELETEをすると、Redisからレコードを削除します。
curl -X DELETE http://localhost:8080/increments/first
curl http://localhost:8080/increments
# -> []
※リクエストで指定されたキーがRedisサーバにないときのエラーハンドリングはしていないので、キー指定が間違っていると例外が発生します。
curl http://localhost:8080/increments/second
# -> java.lang.NullPointerException: Cannot invoke "io.vertx.redis.client.Response.toString()" because the return value of "io.quarkus.redis.client.RedisClient.get(String)" is null
mvnパッケージングと実行
JVMで動かす場合
# パッケージング
./mvnw package
# 起動
java -jar target/quarkus-app/quarkus-run.jar
Nativeで動かす場合
Quarkusを使う利点の1つである、ネイティブコンパイルを行い起動する方法です。
こちらの方法によるパッケージングには少々時間がかかります。(Intel Corei7-10710uで2分半程度。PCがめっちゃ熱くなりました。)
# パッケージング
./mvnw package -Pnative
# 起動
./target/redis-quickstart-1.0.0-SNAPSHOT-runner
その他の操作
こちらのページでは、上述の手順以外に下記のような手順が掲載されています。
- コネクションのヘルスチェック
- 複数Redisクライアント
コンフィグレーション
工事中