0
0

More than 3 years have passed since last update.

Quarkus+Redisで簡単なアプリケーションを作る

Last updated at Posted at 2021-05-16

概要

最近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に以下を追記してください。

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

ソースコードの解説は時間があるときにやります(やらないとは言っていない…言ってない!)

src/main/java/org/acme/redis/Increment.java
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

src/main/java/org/acme/redis/IncrementService.java
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

src/main/java/org/acme/redis/IncrementResource.java
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

こちらはオプショナルです。

src/test/java/org/acme/redis/IncrementResourceTest.java
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.hamcrestio.restassuredのライブラリが見つからず、エラーとなりました。
エラーが発生した場合は、以下をpom.xmlに追加してください。

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秒くらいで起動できてますね。)
image.png

作成したアプリケーションを試してみる

起動が完了すると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クライアント

コンフィグレーション

工事中

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0