Make app boot without Redis: gate Valkey beans at build time, disable redis health, default OIDC off

This commit is contained in:
a
2026-06-12 22:59:18 +01:00
parent 90e8e34199
commit 4665cceeb3
8 changed files with 52 additions and 24 deletions
@@ -50,6 +50,14 @@ quarkus.http.so-reuse-port=false
# - spring.web.resources.mime-mappings.webmanifest=application/manifest+json
# - server.servlet.session.tracking-modes=cookie (configure on quarkus-undertow)
# ---- OIDC / OAuth2 login -----------------------------------------------------------------------
# The quarkus-oidc extension is on the classpath for OAuth2/OIDC login, but it FAILS STARTUP if
# enabled without quarkus.oidc.auth-server-url. Under Spring, OAuth2 login was activated only when
# the operator configured a provider; the default (and the login-disabled / ultra-lite deployment)
# ran with no OIDC at all. Mirror that: OIDC is off by default and an OAuth2 deployment re-enables
# it by setting quarkus.oidc.enabled=true plus quarkus.oidc.auth-server-url/client-id/... .
quarkus.oidc.enabled=false
# ---- Error handling (was spring.web.error.* / spring.mvc.problemdetails.enabled=false) --------
# TODO: Migration required - GlobalExceptionHandler is an @ControllerAdvice; rewrite as JAX-RS
# ExceptionMapper(s) producing RFC 7807 ProblemDetail responses. The Spring error-page / whitelabel
@@ -113,6 +121,12 @@ v2=true
# spring.config.additional-location. Quarkus uses a different config-source mechanism
# (SmallRye Config / quarkus.config.locations). Port ConfigInitializer accordingly.
# ---- Redis auto-config exclusion -------------------------------------------------------------
# ---- Redis / Valkey backplane ----------------------------------------------------------------
# spring.autoconfigure.exclude (Redis) -> not applicable; Quarkus only wires the Redis client when
# quarkus.redis.* is configured, so no dead localhost:6379 factory is created by default.
# The Redis client is only used by the optional Valkey cluster backplane (cluster.enabled=true AND
# cluster.backplane=valkey). In the default single-node deployment no quarkus.redis.hosts is set, so
# the RedisDataSource bean is INACTIVE. The Redis readiness health check is a startup observer that
# eagerly resolves that inactive bean and aborts boot - disable it here; a Valkey deployment that
# configures quarkus.redis.hosts can re-enable it with quarkus.redis.health.enabled=true.
quarkus.redis.health.enabled=false
@@ -1,5 +1,6 @@
package stirling.software.proprietary.cluster.valkey;
import io.quarkus.arc.properties.IfBuildProperty;
import io.quarkus.redis.datasource.RedisDataSource;
import jakarta.enterprise.context.ApplicationScoped;
@@ -10,15 +11,13 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.common.cluster.ClusterBackplane;
import stirling.software.common.model.ApplicationProperties;
// Build-time gating: this bean (and its RedisDataSource dependency) is only included in the build
// when cluster.backplane=valkey. With the default backplane (inprocess) the whole Valkey bean is
// removed, so RedisDataSource has no consumers and Quarkus emits no eager startup observer for the
// inactive Redis client - the in-process @DefaultBean ClusterBackplane satisfies the interface.
@Slf4j
@IfBuildProperty(name = "cluster.backplane", stringValue = "valkey")
@ApplicationScoped
// TODO: Migration required - @ConditionalOnValkeyBackplane was a Spring @ConditionalOnExpression
// guard ("cluster.enabled=true AND cluster.backplane=valkey"). Quarkus has no runtime
// @Conditional for beans; this bean is now always instantiated. Gate selection at runtime
// (e.g. a ClusterBackplane producer that picks valkey vs in-process based on injected config),
// or use @io.quarkus.arc.lookup.LookupIfProperty(name="cluster.backplane", stringValue="valkey")
// (build-time/static only - does not also check cluster.enabled). The composite condition must be
// re-expressed accordingly.
public class ValkeyClusterBackplane implements ClusterBackplane {
@Inject ApplicationProperties applicationProperties;
@@ -6,7 +6,7 @@ import java.time.Duration;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.quarkus.arc.lookup.LookupIfProperty;
import io.quarkus.arc.properties.IfBuildProperty;
import io.quarkus.redis.datasource.RedisDataSource;
import jakarta.enterprise.context.ApplicationScoped;
@@ -53,10 +53,15 @@ import stirling.software.common.model.ApplicationProperties.Cluster;
// so
// the injected RedisDataSource targets the right Valkey. parseUrl/buildClientConfiguration are kept
// to validate the URL and to drive that config mapping once it is wired.
// Build-time gating: the whole producer class (RedisDataSource consumer + RedisClient producer) is
// only included in the build when cluster.backplane=valkey. With the default backplane the class
// and
// its producers are removed, so the inactive Redis client has no consumers and no eager startup
// observer is generated.
@Slf4j
@ApplicationScoped
@RequiredArgsConstructor
@LookupIfProperty(name = "cluster.enabled", stringValue = "true")
@IfBuildProperty(name = "cluster.backplane", stringValue = "valkey")
public class ValkeyConnectionConfiguration {
private final ApplicationProperties applicationProperties;
@@ -298,7 +303,6 @@ public class ValkeyConnectionConfiguration {
// RedisURI once cluster.valkey credentials handling is finalised.
@Produces
@Singleton
@LookupIfProperty(name = "cluster.backplane", stringValue = "valkey")
public RedisClient nativeRedisClient() {
Endpoint endpoint = parseUrl(applicationProperties.getCluster().getValkey().getUrl());
RedisURI.Builder uri =
@@ -4,7 +4,7 @@ import java.time.Duration;
import java.util.Optional;
import java.util.UUID;
import io.quarkus.arc.lookup.LookupIfProperty;
import io.quarkus.arc.properties.IfBuildProperty;
import io.quarkus.redis.datasource.RedisDataSource;
import io.quarkus.redis.datasource.value.SetArgs;
import io.quarkus.redis.datasource.value.ValueCommands;
@@ -34,9 +34,10 @@ import stirling.software.common.cluster.DistributedLock;
// The injected bean is now the RedisDataSource that ValkeyConnectionConfiguration produces; the Lua
// scripts and the acquire/release/renew control flow are framework-agnostic and carry over
// unchanged.
// Build-time gating: included in the build only when cluster.backplane=valkey; otherwise this bean
// (and its RedisDataSource dependency) is removed so no eager Redis startup observer is generated.
@ApplicationScoped
@LookupIfProperty(name = "cluster.enabled", stringValue = "true")
@LookupIfProperty(name = "cluster.backplane", stringValue = "valkey")
@IfBuildProperty(name = "cluster.backplane", stringValue = "valkey")
@Slf4j
public class ValkeyDistributedLock implements DistributedLock {
@@ -27,10 +27,10 @@ import stirling.software.common.cluster.InstanceRegistry;
* Valkey-backed {@link InstanceRegistry}. Each node is stored as a hash with a TTL equal to the
* configured heartbeat TTL; the heartbeat re-arms the TTL.
*/
// TODO: Migration required - the original @ConditionalOnValkeyBackplane (cluster.enabled=true AND
// cluster.backplane=valkey) was a runtime toggle. Quarkus build-time conditions (@IfBuildProfile /
// @LookupIfProperty) cannot express this composite runtime expression. Guard producer/usage at
// runtime via the Config values, or rework ConditionalOnValkeyBackplane into a CDI lookup guard.
// Build-time gating: included in the build only when cluster.backplane=valkey; otherwise this bean
// (and its RedisDataSource dependency) is removed so no eager Redis startup observer is generated
// and the in-process @DefaultBean InstanceRegistry satisfies the interface.
@io.quarkus.arc.properties.IfBuildProperty(name = "cluster.backplane", stringValue = "valkey")
@ApplicationScoped
@RequiredArgsConstructor
public class ValkeyInstanceRegistry implements InstanceRegistry {
@@ -44,6 +44,12 @@ import stirling.software.common.cluster.JobStoreEntry;
// build-time condition (@io.quarkus.arc.profile.IfBuildProfile /
// @io.quarkus.arc.lookup.LookupIfProperty) or guard bean activation at runtime. Annotation left in
// place pending that collaborator change.
// Build-time gating: included in the build only when cluster.backplane=valkey; otherwise this bean
// (and its RedisDataSource dependency) is removed so no eager Redis startup observer is generated
// and the in-process @DefaultBean JobStore satisfies the interface. @ConditionalOnValkeyBackplane
// is
// kept for documentation only (it does not propagate guards through Arc).
@io.quarkus.arc.properties.IfBuildProperty(name = "cluster.backplane", stringValue = "valkey")
@ApplicationScoped
@ConditionalOnValkeyBackplane
@Slf4j
@@ -21,6 +21,10 @@ import stirling.software.common.cluster.KeyValueCache;
// @io.quarkus.arc.lookup.LookupIfProperty
// or @io.quarkus.arc.profile.IfBuildProfile, or a runtime guard) so Valkey beans only load when
// cluster.enabled=true AND cluster.backplane=valkey.
// Build-time gating: included in the build only when cluster.backplane=valkey; otherwise this bean
// (and its RedisDataSource dependency) is removed so no eager Redis startup observer is generated
// and the in-process @DefaultBean KeyValueCache satisfies the interface.
@io.quarkus.arc.properties.IfBuildProperty(name = "cluster.backplane", stringValue = "valkey")
@ApplicationScoped
public class ValkeyKeyValueCache implements KeyValueCache {
@@ -11,7 +11,7 @@ import io.github.bucket4j.distributed.proxy.ProxyManager;
import io.github.bucket4j.redis.lettuce.Bucket4jLettuce;
import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.RedisClient;
import io.quarkus.arc.lookup.LookupIfProperty;
import io.quarkus.arc.properties.IfBuildProperty;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@@ -25,13 +25,13 @@ import stirling.software.common.cluster.RateLimitStore;
* refills continuously and enforces one global limit across nodes, with the same semantics as the
* in-process {@code InProcessRateLimitStore} (which also uses Bucket4j).
*/
// @ConditionalOnValkeyBackplane is documentary only under CDI (see that annotation's javadoc);
// the two guards below must be carried directly so the Valkey beans load only when
// cluster.enabled=true AND cluster.backplane=valkey, otherwise the in-process @DefaultBean wins.
// Build-time gating: included in the build only when cluster.backplane=valkey; otherwise this bean
// (and its RedisClient dependency) is removed so no eager Redis startup observer is generated and
// the in-process @DefaultBean RateLimitStore wins. @ConditionalOnValkeyBackplane is documentary
// only.
@ApplicationScoped
@ConditionalOnValkeyBackplane
@LookupIfProperty(name = "cluster.enabled", stringValue = "true")
@LookupIfProperty(name = "cluster.backplane", stringValue = "valkey")
@IfBuildProperty(name = "cluster.backplane", stringValue = "valkey")
public class ValkeyRateLimitStore implements RateLimitStore {
private static final String PREFIX = "stirling:rl:";