Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce factory methods to create Redis clients dynamically #17739

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/src/main/asciidoc/redis.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@ So when you access the `/q/health/ready` endpoint of your application you will h

This behavior can be disabled by setting the `quarkus.redis.health.enabled` property to `false` in your `application.properties`.

[[multiple-clients-configuration]]
== Multiple Redis Clients

The Redis extension allows you to configure multiple clients.
Expand Down Expand Up @@ -585,6 +586,36 @@ RedisClient redisClient2;
ReactiveRedisClient reactiveClient2;
----

=== Creating Clients Programmatically

The `RedisClient` and `ReactiveRedisClient` provide factory methods to create clients programmatically.
The client to be created are configured using the usual <<config-reference,Redis configuration>>.

machi1990 marked this conversation as resolved.
Show resolved Hide resolved
[NOTE]
====
This is useful to create a client dynamically in a non-CDI bean e.g a link:hibernate-orm-panache.adoc[Panache entity].
Or to create a different client when running in pub/sub mode. This mode requires two different connections
because once a connection invokes a subscriber mode then it cannot be used for running other commands
than the command to leave that mode.
====

The below code snippet shows how we can create dynamic clients using the configurations in <<multiple-clients-configuration>>.
[source,java,indent=0]
----
// creating default redis client
RedisClient defaultRedisClient = RedisClient.createClient();

// creating named redis client whose configuration name is "second"
RedisClient namedRedisClient = RedisClient.createClient("second");

// creating a default reactive redis client
ReactiveRedisClient defaultReactiveRedisClient = ReactiveRedisClient.createClient();

// creating a named reactive redis client whose configuration name is "second"
ReactiveRedisClient namedReactiveRedisClient = ReactiveRedisClient.createClient("second");
----


[[dev-services]]
=== DevServices (Configuration Free Redis)

Expand All @@ -599,6 +630,7 @@ When running the production version of the application, the Redis connection nee
so if you want to include a production database config in your `application.properties` and continue to use DevServices
we recommend that you use the `%prod.` profile to define your Redis settings.

[[config-reference]]
== Configuration Reference

include::{generated-dir}/config/quarkus-redis-client.adoc[opts=optional, leveloffset=+1]
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ List<AdditionalBeanBuildItem> registerRedisBeans() {
return Arrays.asList(
AdditionalBeanBuildItem
.builder()
.addBeanClass("io.quarkus.redis.client.runtime.RedisAPIProducer")
.addBeanClass("io.quarkus.redis.client.runtime.RedisClientsProducer")
.setDefaultScope(SINGLETON.getName())
.setUnremovable()
.build(),
Expand Down
10 changes: 10 additions & 0 deletions extensions/redis-client/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@
<artifactId>quarkus-smallrye-health</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package io.quarkus.redis.client;

import static io.quarkus.redis.client.runtime.RedisClientUtil.DEFAULT_CLIENT;

import java.util.List;

import io.quarkus.arc.Arc;
import io.quarkus.redis.client.runtime.RedisClientsProducer;
import io.vertx.redis.client.Response;

/**
Expand All @@ -13,6 +17,25 @@
* the <a href="https://redis.io/commands">Redis Commands Page</a>
*/
public interface RedisClient {
/**
* Creates the {@link RedisClient} using the default redis client configuration
*
* @return {@link RedisClient} - the default redis client
*/
static RedisClient createClient() {
return createClient(DEFAULT_CLIENT);
}

/**
* Creates the {@link RedisClient} using the named redis client configuration
*
* @return {@link RedisClient} - the named client
*/
static RedisClient createClient(String name) {
RedisClientsProducer redisClientsProducer = Arc.container().instance(RedisClientsProducer.class).get();
return redisClientsProducer.getRedisClient(name);
}

void close();

Response append(String arg0, String arg1);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package io.quarkus.redis.client.reactive;

import static io.quarkus.redis.client.runtime.RedisClientUtil.DEFAULT_CLIENT;

import java.util.List;

import io.quarkus.arc.Arc;
import io.quarkus.redis.client.RedisClient;
import io.quarkus.redis.client.runtime.RedisClientsProducer;
import io.smallrye.mutiny.Uni;
import io.vertx.mutiny.redis.client.Response;

Expand All @@ -12,6 +17,25 @@
* the <a href="https://redis.io/commands">Redis Commands Page</a>
*/
public interface ReactiveRedisClient {
/**
* Creates the {@link RedisClient} using the default redis client configuration
*
* @return {@link ReactiveRedisClient} - the default reactive redis client
*/
static ReactiveRedisClient createClient() {
return createClient(DEFAULT_CLIENT);
}

/**
* Creates the {@link RedisClient} using the named redis client configuration
*
* @return {@link ReactiveRedisClient} - the named reactive redis client
*/
static ReactiveRedisClient createClient(String name) {
RedisClientsProducer redisClientsProducer = Arc.container().instance(RedisClientsProducer.class).get();
return redisClientsProducer.getReactiveRedisClient(name);
}

void close();

Uni<Response> append(String arg0, String arg1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public Redis get() {
}

private RedisAPIContainer getRedisAPIContainer(String clientName) {
RedisAPIProducer redisAPIProducer = Arc.container().instance(RedisAPIProducer.class).get();
return redisAPIProducer.getRedisAPIContainer(clientName);
RedisClientsProducer redisClientsProducer = Arc.container().instance(RedisClientsProducer.class).get();
return redisClientsProducer.getRedisAPIContainer(clientName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,16 @@ public static boolean isDefault(String clientName) {
}

public static RedisConfiguration getConfiguration(RedisConfig config, String name) {
return isDefault(name) ? config.defaultClient : config.additionalRedisClients.get(name);
if (isDefault(name)) {
return config.defaultClient;
}

RedisConfiguration redisConfiguration = config.additionalRedisClients.get(name);
if (redisConfiguration != null) {
return redisConfiguration;
}

throw new IllegalArgumentException(String.format("Configuration for %s redis client does not exists", name));
}

public static RedisHostsProvider findProvider(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
import io.vertx.redis.client.RedisAPI;
import io.vertx.redis.client.RedisOptions;

class RedisAPIProducer {
public class RedisClientsProducer {
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(10);
private static Map<String, RedisAPIContainer> REDIS_APIS = new ConcurrentHashMap<>();

private final Vertx vertx;
private final RedisConfig redisConfig;

public RedisAPIProducer(RedisConfig redisConfig, Vertx vertx) {
public RedisClientsProducer(RedisConfig redisConfig, Vertx vertx) {
this.redisConfig = redisConfig;
this.vertx = vertx;
}
Expand All @@ -30,12 +31,9 @@ public RedisAPIContainer getRedisAPIContainer(String name) {
return REDIS_APIS.computeIfAbsent(name, new Function<String, RedisAPIContainer>() {
@Override
public RedisAPIContainer apply(String s) {
Duration timeout = Duration.ofSeconds(10);
RedisConfiguration redisConfiguration = RedisClientUtil.getConfiguration(RedisAPIProducer.this.redisConfig,
RedisConfiguration redisConfiguration = RedisClientUtil.getConfiguration(RedisClientsProducer.this.redisConfig,
name);
if (redisConfiguration.timeout.isPresent()) {
timeout = redisConfiguration.timeout.get();
}
Duration timeout = redisConfiguration.timeout.orElse(DEFAULT_TIMEOUT);
RedisOptions options = RedisClientUtil.buildOptions(redisConfiguration);
Redis redis = Redis.createClient(vertx, options);
RedisAPI redisAPI = RedisAPI.api(redis);
Expand All @@ -48,6 +46,27 @@ public RedisAPIContainer apply(String s) {
});
}

public RedisClient getRedisClient(String name) {
RedisConfiguration redisConfiguration = RedisClientUtil.getConfiguration(RedisClientsProducer.this.redisConfig,
name);
Duration timeout = redisConfiguration.timeout.orElse(DEFAULT_TIMEOUT);
RedisOptions options = RedisClientUtil.buildOptions(redisConfiguration);
Redis redis = Redis.createClient(vertx, options);
RedisAPI redisAPI = RedisAPI.api(redis);
MutinyRedisAPI mutinyRedisAPI = new MutinyRedisAPI(redisAPI);
return new RedisClientImpl(mutinyRedisAPI, timeout);
}

public ReactiveRedisClient getReactiveRedisClient(String name) {
RedisConfiguration redisConfiguration = RedisClientUtil.getConfiguration(RedisClientsProducer.this.redisConfig,
name);
RedisOptions options = RedisClientUtil.buildOptions(redisConfiguration);
Redis redis = Redis.createClient(vertx, options);
RedisAPI redisAPI = RedisAPI.api(redis);
MutinyRedisAPI mutinyRedisAPI = new MutinyRedisAPI(redisAPI);
return new ReactiveRedisClientImpl(mutinyRedisAPI);
}

@PreDestroy
public void close() {
for (RedisAPIContainer container : REDIS_APIS.values()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.quarkus.redis.client.runtime;

import static io.quarkus.redis.client.runtime.RedisClientUtil.DEFAULT_CLIENT;
import static io.quarkus.redis.client.runtime.RedisClientUtil.getConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.HashMap;

import org.junit.jupiter.api.Test;

import io.quarkus.redis.client.runtime.RedisConfig.RedisConfiguration;

class RedisClientUtilTest {

@Test
void testGetConfiguration() {
RedisConfig config = new RedisConfig();
config.defaultClient = new RedisConfiguration();
config.additionalRedisClients = new HashMap<>();
RedisConfiguration namedConfiguration = new RedisConfiguration();
config.additionalRedisClients.put("some-configuration", namedConfiguration);

// get default client configuration
RedisConfiguration configuration = getConfiguration(config, DEFAULT_CLIENT);
assertThat(configuration).isEqualTo(config.defaultClient);

// get named client configuration
configuration = getConfiguration(config, "some-configuration");
assertThat(configuration).isEqualTo(namedConfiguration);

// throw error for non-existing named client
assertThatThrownBy(() -> {
getConfiguration(config, "non-existing");
}).isInstanceOf(IllegalArgumentException.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.quarkus.redis.it;

import java.util.Arrays;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

import io.quarkus.redis.client.RedisClient;
import io.quarkus.redis.client.reactive.ReactiveRedisClient;
import io.smallrye.mutiny.Uni;
import io.vertx.redis.client.Response;

@Path("/quarkus-redis-dynamic-client-creation")
@ApplicationScoped
public class RedisWithDynamicClientCreationResource {
// synchronous
@GET
@Path("/sync/{key}")
public String getSync(@PathParam("key") String key) {
Response response = RedisClient.createClient("dynamic").get(key);
return response == null ? null : response.toString();
}

@POST
@Path("/sync/{key}")
public void setSync(@PathParam("key") String key, String value) {
RedisClient.createClient("dynamic").set(Arrays.asList(key, value));
}

// reactive
@GET
@Path("/reactive/{key}")
public Uni<String> getReactive(@PathParam("key") String key) {
return ReactiveRedisClient.createClient("dynamic")
.get(key)
.map(response -> response == null ? null : response.toString());
}

@POST
@Path("/reactive/{key}")
public Uni<Void> setReactive(@PathParam("key") String key, String value) {
return ReactiveRedisClient.createClient("dynamic")
.set(Arrays.asList(key, value))
.map(response -> null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ quarkus.redis.named-client.hosts=redis://localhost:6379/1
quarkus.redis.parameter-injection.hosts=redis://localhost:6379/2
quarkus.redis.named-reactive-client.hosts=redis://localhost:6379/1
quarkus.redis.provided-hosts.hosts-provider-name=test-hosts-provider
quarkus.redis.dynamic.hosts=redis://localhost:6379/4
Loading