Skip to content

Commit

Permalink
feat(redis): introduce factory methods to create clients dynamically
Browse files Browse the repository at this point in the history
Closes #13887
  • Loading branch information
machi1990 committed Jun 8, 2021
1 parent e54aaf9 commit 3f3178a
Show file tree
Hide file tree
Showing 18 changed files with 271 additions and 263 deletions.
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>>.

[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

0 comments on commit 3f3178a

Please sign in to comment.