Skip to content

Commit

Permalink
Merge pull request #26803 from cescoffier/redis-options-customizer
Browse files Browse the repository at this point in the history
Add a way to customize the Redis client options
  • Loading branch information
gsmet authored Jul 19, 2022
2 parents 8db6af3 + 841eff3 commit b4527f8
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 9 deletions.
23 changes: 23 additions & 0 deletions docs/src/main/asciidoc/redis-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,29 @@ The host provider can be used to configure the redis client like shown below
quarkus.redis.hosts-provider-name=hosts-provider
----

== Customizing the Redis options programmatically

You can expose a bean implementing the `io.quarkus.redis.client.RedisOptionsCustomizer` interface to customize the Redis client options.
The bean is called for each configured Redis client:

[source, java]
----
@ApplicationScoped
public static class MyExampleCustomizer implements RedisOptionsCustomizer {
@Override
public void customize(String clientName, RedisOptions options) {
if (clientName.equalsIgnoreCase("my-redis")
|| clientName.equalsIgnoreCase(RedisConfig.DEFAULT_CLIENT_NAME)) {
// modify the given options
} else {
throw new IllegalStateException("Unknown client name: " + clientName);
}
}
}
----


=== Dev Services

See link:redis-dev-services.adoc[Redis Dev Service].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import io.quarkus.redis.client.RedisClient;
import io.quarkus.redis.client.RedisClientName;
import io.quarkus.redis.client.RedisHostsProvider;
import io.quarkus.redis.client.RedisOptionsCustomizer;
import io.quarkus.redis.client.reactive.ReactiveRedisClient;
import io.quarkus.redis.runtime.client.RedisClientRecorder;
import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem;
Expand Down Expand Up @@ -89,8 +90,8 @@ List<AdditionalBeanBuildItem> registerRedisClientName() {
}

@BuildStep
UnremovableBeanBuildItem makeHostsProviderUnremovable() {
return UnremovableBeanBuildItem.beanTypes(RedisHostsProvider.class);
UnremovableBeanBuildItem makeHostsProviderAndOptionsCustomizerUnremovable() {
return UnremovableBeanBuildItem.beanTypes(RedisHostsProvider.class, RedisOptionsCustomizer.class);
}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.quarkus.redis.client.deployment;

import java.util.List;
import java.util.UUID;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

import org.assertj.core.api.Assertions;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.redis.client.RedisClientName;
import io.quarkus.redis.client.RedisOptionsCustomizer;
import io.quarkus.redis.runtime.client.config.RedisConfig;
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.QuarkusTestResource;
import io.vertx.mutiny.redis.client.RedisAPI;
import io.vertx.redis.client.RedisOptions;

@QuarkusTestResource(RedisTestResource.class)
public class CustomizerTest {

@RegisterExtension
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClass(MyCustomizer.class))
.overrideConfigKey("quarkus.redis.hosts", "redis://wont-work")
.overrideConfigKey("quarkus.redis.my-redis.hosts", "redis://wont-work");

@Inject
RedisAPI api;

@Inject
@RedisClientName("my-redis")
RedisAPI myapi;

@Test
public void testCustomization() {
String key = UUID.randomUUID().toString();
api.setAndAwait(List.of(key, "test-" + key));
String v = myapi.getAndAwait(key).toString();
Assertions.assertThat(v).isEqualTo("test-" + key);
}

@ApplicationScoped
public static class MyCustomizer implements RedisOptionsCustomizer {

@Override
public void customize(String clientName, RedisOptions options) {
String v = ConfigProviderResolver.instance().getConfig().getValue("quarkus.redis.tr", String.class);
if (clientName.equalsIgnoreCase("my-redis")
|| clientName.equalsIgnoreCase(RedisConfig.DEFAULT_CLIENT_NAME)) {
options.setEndpoints(List.of(v));
} else {
throw new IllegalStateException("Unknown client name: " + clientName);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ public class RedisTestResource implements QuarkusTestResourceLifecycleManager {
@Override
public Map<String, String> start() {
server.start();
return Map.of("quarkus.redis.tr", "redis://" + server.getHost() + ":" + server.getMappedPort(6379));
return Map.of("quarkus.redis.tr", getEndpoint());
}

@Override
public void stop() {
server.stop();
}

public static String getEndpoint() {
return String.format("redis://%s:%s", server.getHost(), server.getMappedPort(6379));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkus.redis.client;

import io.vertx.redis.client.RedisOptions;

/**
* Beans exposing the {@code RedisClientOptionsCustomizer} interface has the possibility to extend/modify the
* {@link io.vertx.redis.client.RedisOptions} before they are used to create the {@code RedisClient} or
* {@code RedisDataSource}.
*/
public interface RedisOptionsCustomizer {

/**
* Allows customizing the options for the client named {@code clientName}.
* The passed {@code options} must be modified <em>in-place</em>.
*
* @param clientName the client name, {@link io.quarkus.redis.runtime.client.config.RedisConfig#DEFAULT_CLIENT_NAME}
* for the default client.
* @param options the options.
*/
void customize(String clientName, RedisOptions options);

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ public class RedisClientRecorder {
// Split client and DS recorders

private final RedisConfig config;
private final Map<String, RedisClientAndApi> clients = new HashMap<>();

private final Map<String, ReactiveRedisDataSourceImpl> dataSources = new HashMap<>();
private static final Map<String, RedisClientAndApi> clients = new HashMap<>();
private static final Map<String, ReactiveRedisDataSourceImpl> dataSources = new HashMap<>();

public RedisClientRecorder(RedisConfig rc) {
this.config = rc;
Expand All @@ -44,6 +43,14 @@ public void initialize(RuntimeValue<io.vertx.core.Vertx> vertx, Set<String> name
_initialize(v, names);
}

private void closeAllClients() {
for (Map.Entry<String, RedisClientAndApi> entry : clients.entrySet()) {
entry.getValue().redis.close();
}
clients.clear();
dataSources.clear();
}

public void _initialize(Vertx vertx, Set<String> names) {
for (String name : names) {
// Search if we have an associated config:
Expand All @@ -63,9 +70,11 @@ public ConfigurationException get() {
"You must at least configure `quarkus.redis." + name + ".hosts`.");
}
});
clients.computeIfAbsent(name, x -> new RedisClientAndApi(VertxRedisClientFactory.create(vertx, actualConfig)));
clients.computeIfAbsent(name,
x -> new RedisClientAndApi(VertxRedisClientFactory.create(name, vertx, actualConfig)));
} else if (DEFAULT_CLIENT_NAME.equalsIgnoreCase(name) && maybe.isPresent()) {
clients.computeIfAbsent(name, x -> new RedisClientAndApi(VertxRedisClientFactory.create(vertx, maybe.get())));
clients.computeIfAbsent(name,
x -> new RedisClientAndApi(VertxRedisClientFactory.create(DEFAULT_CLIENT_NAME, vertx, maybe.get())));
}
// Do not throw an error. We would need to check if the default redis client is used.
}
Expand Down Expand Up @@ -188,6 +197,7 @@ public void run() {
value.redis.close();
}
clients.clear();
dataSources.clear();
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.InjectableInstance;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.redis.client.RedisHostsProvider;
import io.quarkus.redis.client.RedisOptionsCustomizer;
import io.quarkus.redis.runtime.client.config.NetConfig;
import io.quarkus.redis.runtime.client.config.RedisClientConfig;
import io.quarkus.redis.runtime.client.config.TlsConfig;
Expand All @@ -38,7 +40,7 @@ private VertxRedisClientFactory() {
// Avoid direct instantiation.
}

public static Redis create(Vertx vertx, RedisClientConfig config) {
public static Redis create(String name, Vertx vertx, RedisClientConfig config) {
RedisOptions options = new RedisOptions();

List<URI> hosts = new ArrayList<>();
Expand Down Expand Up @@ -81,9 +83,21 @@ public static Redis create(Vertx vertx, RedisClientConfig config) {
config.replicas.ifPresent(options::setUseReplicas);

options.setNetClientOptions(toNetClientOptions(config));

customize(name, options);

return Redis.createClient(vertx, options);
}

private static void customize(String name, RedisOptions options) {
if (Arc.container() != null) {
List<InstanceHandle<RedisOptionsCustomizer>> customizers = Arc.container().listAll(RedisOptionsCustomizer.class);
for (InstanceHandle<RedisOptionsCustomizer> customizer : customizers) {
customizer.get().customize(name, options);
}
}
}

private static NetClientOptions toNetClientOptions(RedisClientConfig config) {
NetConfig tcp = config.tcp;
TlsConfig tls = config.tls;
Expand Down

0 comments on commit b4527f8

Please sign in to comment.