これは、なにをしたくて書いたもの?
SmallRye Mutiny Vert.x Bindingsを単体でも使ってみようかなと思い、Redis Clientを使って遊ぼうとしていたのですが。
AssertSubscriberを一緒に使っていたら思わずハマったので、まとめてメモ。
SmallRye Mutiny Vert.x Bindingsとは
以前、このブログで少し触れたことがあります。
Vert.x API Generationをちょっと調べつつ、Vert.xのRedis ClientをSmallRye Mutinyに対応させてみる - CLOVER🍀
この時はSmallRye Reactive Utilsという名前(リポジトリ名)だったのですが、気づくとSmallRye Mutiny Vert.x Bindings
という名前になっていました。
GitHub - smallrye/smallrye-mutiny-vertx-bindings: Smallrye Mutiny bindings for Eclipse Vert.x
現在のバージョンは2.13で、リポジトリ内には各種クライアントに対応するプロジェクトが含まれています。
https://github.com/smallrye/smallrye-mutiny-vertx-bindings/tree/2.13.0/vertx-mutiny-clients
といっても、このプロジェクトはVert.x API Generationを使ってソースコードを自動生成するので、リポジトリ内に
実体はほとんどありませんが。
ドキュメントもあるようです。
Smallrye Mutiny Vert.x Bindings project
Smallrye Mutiny Vert.x bindings
Overview (SmallRye Mutiny - Client APIs 2.13.0 API)
SmallRyeのWebサイトやSmallRye Mutinyのサイトから直接辿れない気はするのですが…。
よく見ると、SmallRye MutinyのGetting Startedページに少し登場します。
Getting Started with Mutiny / Using Mutiny with Eclipse Vert.x
それで、今回はRedis Clientを使ってみようかなと思います。
対応するSmallRye Mutiny Vert.x Bindingsのライブラリは、こちらです。
そういえば、このディレクトリってvertx-mutiny-clients
という名前なのに、Vert.x CoreのSmallRye Mutiny Vert.x Bindingsまで
ありますね。いいんですけど。
環境
今回の環境は、こちらです。
$ java --version openjdk 11.0.11 2021-04-20 OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04) OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.2 (ea98e05a04480131370aa0c110b8c54cf726c06f) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-84-generic", arch: "amd64", family: "unix"
Redisは6.2.5を使い、172.17.0.2のIPアドレスで動作しているものとします。また、パスワードはredispass
で設定している
ものとします。
準備
Maven依存関係はこちら。
<dependencies> <dependency> <groupId>io.smallrye.reactive</groupId> <artifactId>smallrye-mutiny-vertx-redis-client</artifactId> <version>2.13.0</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.7.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.7.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.20.2</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> </plugin> </plugins> </build>
今回の主は、こちらのsmallrye-mutiny-vertx-redis-client
ですね。
<dependency> <groupId>io.smallrye.reactive</groupId> <artifactId>smallrye-mutiny-vertx-redis-client</artifactId> <version>2.13.0</version> </dependency>
よく見ると、SmallRye Mutinyって1.0.0になっていたんですね。
<mutiny.version>1.0.0</mutiny.version>
https://github.com/smallrye/smallrye-mutiny-vertx-bindings/blob/2.13.0/pom.xml#L50
Release 1.0.0 · smallrye/smallrye-mutiny · GitHub
テストコードの雛形
確認は、テストコードで行います。まずは雛形を。
src/test/java/org/littlewings/smallrye/mutiny/RedisClientTest.java
package org.littlewings.smallrye.mutiny; import java.util.List; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.helpers.test.AssertSubscriber; import io.smallrye.mutiny.helpers.test.UniAssertSubscriber; import io.vertx.mutiny.core.Vertx; import io.vertx.mutiny.redis.client.Redis; import io.vertx.mutiny.redis.client.RedisAPI; import io.vertx.mutiny.redis.client.Response; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; public class RedisClientTest { // ここに、テストを書く!! }
import文がちょっとポイントですが、Vert.xのクラスを含め、SmallRye Mutinyのものを使います。
import io.vertx.mutiny.core.Vertx; import io.vertx.mutiny.redis.client.Redis; import io.vertx.mutiny.redis.client.RedisAPI; import io.vertx.mutiny.redis.client.Response;
簡単にset/get/delしてみる
SmallRye Mutiny Vert.x Bindingsを使って、Redisにアクセスしてみます。簡単にset/get/delしてみましょう。
@Test public void gettingStarted() { Vertx vertx = Vertx.vertx(); Redis redis = Redis.createClient(vertx, "redis://:redispass@172.17.0.2:6379/0"); RedisAPI redisApi = RedisAPI.api(redis); Uni<Response> setResponse = redisApi.set(List.of("key1", "value1")); Uni<Response> getResponse = setResponse .onItem() .transformToUni(r -> redisApi.get("key1")); UniAssertSubscriber<Response> getAssertSubscriber = getResponse .subscribe() .withSubscriber(UniAssertSubscriber.create()); Response actualGetResponse = getAssertSubscriber .awaitItem() .assertCompleted() .getItem(); assertThat(actualGetResponse).asString().isEqualTo("value1"); Uni<Response> delResponse = redisApi.del(List.of("key1")); Uni<Response> emptyResponse = delResponse .onItem() .transformToUni(r -> redisApi.get("key1")); UniAssertSubscriber<Response> emptyAssertSubscriber = emptyResponse .subscribe() .withSubscriber(UniAssertSubscriber.create()); Response actualEmptyResponse = emptyAssertSubscriber .awaitItem() .assertCompleted() .getItem(); assertThat(actualEmptyResponse).isNull(); redisApi.close(); vertx.closeAndAwait(); }
set、get。
Uni<Response> setResponse = redisApi.set(List.of("key1", "value1")); Uni<Response> getResponse = setResponse .onItem() .transformToUni(r -> redisApi.get("key1"));
getの結果はUni
で返ってくるので、確認にはUniAssertSubscriber
を使いましょう。
UniAssertSubscriber<Response> getAssertSubscriber =
getResponse
.subscribe()
.withSubscriber(UniAssertSubscriber.create());
Response actualGetResponse =
getAssertSubscriber
.awaitItem()
.assertCompleted()
.getItem();
assertThat(actualGetResponse).asString().isEqualTo("value1");
ちょっとハマったのがawaitItem
で、これをつけないと動かなかったのですが…なにがマズいんでしょうね?
オマケ:Multiで試す
UniAssertSubscriber
でちょっとハマったので、Multi
も扱ってみることにしました。
Redis Clientで直接Multi
を返すものはなかったので、mgetの結果をMulti
に変換して扱うことにします。
まあ、これはこれでハマる理由になったのですが。
作成したテストコードは、こちら。
@Test public void assertMulti() { Vertx vertx = Vertx.vertx(); Redis redis = Redis.createClient(vertx, "redis://:redispass@172.17.0.2:6379/0"); RedisAPI redisApi = RedisAPI.api(redis); Uni<Response> msetResponse = redisApi.mset(List.of("key1", "value1", "key2", "value2", "key3", "value3")); Uni<Response> mgetResponse = msetResponse.onItem().transformToUni(r -> redisApi.mget(List.of("key1", "key2", "key3"))); Multi<String> multiMgetResponse = mgetResponse .onItem() .transformToMulti(item -> item.toMulti()).onItem().transform(item -> item.toString()); AssertSubscriber<String> mgetAssertSubscriber = multiMgetResponse .subscribe() .withSubscriber(AssertSubscriber.create(10)); List<String> actualMgetResponse = mgetAssertSubscriber .awaitItems(3) .assertCompleted() .getItems(); assertThat(actualMgetResponse).hasSize(3).containsExactly("value1", "value2", "value3"); Uni<Response> delResponse = redisApi.del(List.of("key1", "key2", "key3")); Uni<Response> emptyResponse = delResponse.onItem().transformToUni(r -> redisApi.mget(List.of("key1", "key2", "key3"))); UniAssertSubscriber<Response> emptyAssertSubscriber = emptyResponse .subscribe() .withSubscriber(UniAssertSubscriber.create()); Response actualEmptyResponse = emptyAssertSubscriber .awaitItem() .assertCompleted() .getItem(); assertThat(actualEmptyResponse) .hasSize(3) .containsOnlyNulls(); redisApi.close(); vertx.closeAndAwait(); }
mset/mgetして、mgetの結果は個々の要素をバラしてUni
からMulti
に変換。
Uni<Response> msetResponse = redisApi.mset(List.of("key1", "value1", "key2", "value2", "key3", "value3")); Uni<Response> mgetResponse = msetResponse.onItem().transformToUni(r -> redisApi.mget(List.of("key1", "key2", "key3"))); Multi<String> multiMgetResponse = mgetResponse .onItem() .transformToMulti(item -> item.toMulti()).onItem().transform(item -> item.toString());
Multi
の場合は、AssertSubscriber
を使います。
AssertSubscriber<String> mgetAssertSubscriber = multiMgetResponse .subscribe() .withSubscriber(AssertSubscriber.create(10)); List<String> actualMgetResponse = mgetAssertSubscriber .awaitItems(3) .assertCompleted() .getItems(); assertThat(actualMgetResponse).hasSize(3).containsExactly("value1", "value2", "value3");
ポイントはAssertSubscriber#create
とAssertSubscriber#awaitItems
です。
AssertSubscriber#create
に与える値は、少なくとも期待する要素数以上を指定する必要があります。今回はMulti
に
3つの要素が含まれるので、3以上であればOKです。
.withSubscriber(AssertSubscriber.create(10));
AssertSubscriber#awaitItems
については、期待する数をきっちり指定する必要があります。今回は、3以外だとうまくいかない
ことになります。
mgetAssertSubscriber
.awaitItems(3)
ところで、AssertSubscriber#awaitItems
が必要な理由もやっぱりよくわかりませんでした…。
delしてmget。このコードの場合、データは削除した後なのでmgetの結果はすべてnull
になるのですが、この状態の
Uni
をMulti
に変換しようとするとうまくいかない(null
を「失敗」と見なす模様)のでそのままUni
にすることに
しました…。
Uni<Response> delResponse = redisApi.del(List.of("key1", "key2", "key3")); Uni<Response> emptyResponse = delResponse.onItem().transformToUni(r -> redisApi.mget(List.of("key1", "key2", "key3"))); UniAssertSubscriber<Response> emptyAssertSubscriber = emptyResponse .subscribe() .withSubscriber(UniAssertSubscriber.create()); Response actualEmptyResponse = emptyAssertSubscriber .awaitItem() .assertCompleted() .getItem(); assertThat(actualEmptyResponse) .hasSize(3) .containsOnlyNulls();
これはこれで、ずいぶんハマりましたが…。
まとめ
SmallRye Mutiny Vert.x Bindingsの最近の情報を調べつつ、Redis Clientで遊び、AssertSubscriberでハマってみました。
いつの間にか、SmallRye Mutinyも1.0.0になっていたんですね。
SmallRye Mutiny Vert.x Bindingsの方も含めて、実装、ドキュメントともにいろいろ増えてきて良いなと思います。
もうちょっと扱って慣れていかないと…。