[ 
https://issues.apache.org/jira/browse/CAMEL-24757?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18116088#comment-18116088
 ] 

Guillaume Nodet commented on CAMEL-24757:
-----------------------------------------

Thanks for the detailed write-up — the problem statement is clear and the 
friction point is real.

The `@ConditionalOnBean(CacheManager.class) && 
@ConditionalOnMissingBean(KeyValueRepository.class)` auto-configuration 
approach is sound and fits the camel-spring-boot pattern well. A few things 
worth thinking through before implementing:

*TTL support.* `KeyValueRepository.put(key, value, ttl)` is part of the SPI 
contract, but `org.springframework.cache.Cache` has no TTL API — it's 
provider-specific configuration. The adapter would have to silently ignore the 
TTL argument, which should be clearly documented. Users who need TTL via this 
adapter would have to configure it at the `CacheManager`/spec level (e.g. 
Caffeine spec), not programmatically per-entry.

*Atomicity.* The CAS operations (`putIfAbsent`, `replace`, `delete(key, 
expected)`) rely on Spring Cache's `Cache.putIfAbsent()`, which does not have a 
consistent atomicity guarantee across providers — it's atomic in Caffeine, but 
not necessarily in Redis (Lettuce) or Ehcache. This should be documented, and 
callers relying on CAS semantics (e.g. aggregation pattern) should be aware.

*`keys()` and `size()`.* `org.springframework.cache.Cache` has no method to 
enumerate keys or count entries — this is intentionally excluded from the 
abstraction. The adapter would have to either return an empty set / throw 
`UnsupportedOperationException`, or try casting to the native cache type (which 
breaks the abstraction). Worth deciding upfront how to handle this, as `keys()` 
is part of the `KeyValueRepository` contract.

*Scope.* The auto-configured bean would be a single global 
`KeyValueRepository`, shared across all `state-store:` endpoints (unless they 
override the `repository` option). If the application uses multiple `storeName` 
values, each would resolve to a different Spring `Cache` instance within that 
single `CacheManager` — that's probably fine, but worth spelling out in the 
docs.

None of these are blockers, but they affect what the documentation needs to say 
about the adapter's constraints vs. the native backends. If a PR is coming, I'd 
suggest starting with those four points as explicit `@throws 
UnsupportedOperationException` or javadoc notes in the implementation class.

> camel-spring-boot: Spring Cache-backed KeyValueRepository adapter for the 
> State Store component
> -----------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-24757
>                 URL: https://issues.apache.org/jira/browse/CAMEL-24757
>             Project: Camel
>          Issue Type: New Feature
>          Components: camel-spring-boot
>            Reporter: Federico Mariani
>            Priority: Major
>
> h2. Background
> The State Store component (camel-state-store, since 4.23) provides a unified 
> key-value API (put/get/putIfAbsent/delete/contains/keys/size/clear) backed by 
> the pluggable {{KeyValueRepository}} SPI from camel-api. When no backend bean 
> is configured, it falls back to the in-memory {{MemoryKeyValueRepository}}; 
> for anything persistent/distributed, camel-api already ships implementations 
> for Caffeine, Cassandra, Ehcache, Hazelcast, Infinispan, JCache, JDBC, JPA, 
> Kafka and Redis.
> h2. Problem
> Those implementations don't line up with what Spring Boot users already have 
> auto-configured:
> * {{RedisKeyValueRepository}} (camel-redis) is built on *Redisson* 
> ({{RedissonClient}}), not Spring Data Redis (Lettuce/{{RedisTemplate}}), 
> which is what {{spring-boot-starter-data-redis}} actually auto-configures. A 
> Spring Boot user has to add Redisson separately and hand-wire a 
> {{RedissonClient}} bean just to get a {{KeyValueRepository}}.
> * The same friction applies to Caffeine/Ehcache/Hazelcast: Camel's 
> implementations expect Camel-flavored construction, not the {{CacheManager}} 
> bean that {{spring-boot-starter-cache}} already auto-configures from 
> {{spring.cache.type}}.
> So in a Spring Boot application, using the State Store component with a real 
> backend currently means adding a second, Camel-specific client/dependency and 
> manually wiring a bean, even when Spring Boot already manages an equivalent 
> cache/client for you.
> h2. Proposal
> Add a {{KeyValueRepository}} implementation in *camel-spring-boot* (core 
> auto-configuration module, not the generated starter) backed by Spring's 
> cache abstraction ({{org.springframework.cache.Cache}} / {{CacheManager}}). 
> Auto-configure it with {{@ConditionalOnBean(CacheManager.class)}} and 
> {{@ConditionalOnMissingBean(KeyValueRepository.class)}}, so it only kicks in 
> when the application already has a Spring-managed {{CacheManager}} and no 
> explicit Camel {{KeyValueRepository}} bean.
> One adapter then covers every backend Spring Boot's cache abstraction already 
> supports (Caffeine, Redis via Lettuce, Ehcache, Hazelcast, JCache, Couchbase, 
> etc.) via the standard {{spring.cache.*}} properties, with no extra 
> Camel-specific dependency or bean.
> The State Store endpoint's {{storeName}} would map 1:1 to the Spring 
> {{Cache}} name looked up via {{CacheManager.getCache(storeName)}}.
> h2. Example usage
> Caffeine, using only spring-boot-starter-cache (no camel-caffeine dependency 
> needed):
> {code:yaml}
> spring:
>   cache:
>     type: caffeine
>     cache-names: myStore
>     caffeine:
>       spec: maximumSize=10000,expireAfterWrite=600s
> {code}
> {code:java}
> from("direct:store")
>     .setHeader("CamelStateStoreKey", constant("myKey"))
>     .to("state-store:myStore?operation=put");
> from("direct:retrieve")
>     .setHeader("CamelStateStoreKey", constant("myKey"))
>     .to("state-store:myStore?operation=get");
> {code}
> Redis via Spring Data Redis/Lettuce (no camel-redis/Redisson dependency 
> needed):
> {code}
> spring:
>   cache:
>     type: redis
>     cache-names: myStore
>   data:
>     redis:
>       host: localhost
>       port: 6379
> {code}
> Same route code as above - only the Spring Boot configuration changes to 
> switch backend.
> h2. Limitations
> * *No {{keys()}} support* - Spring's {{Cache}} SPI has no portable key 
> enumeration across backends, so the adapter would throw 
> {{UnsupportedOperationException}} for the {{keys}} operation.
> * *No {{size()}} support* - same reason as above; {{size}} would also throw 
> {{UnsupportedOperationException}}.
> * *No per-entry TTL* - Spring's {{Cache.put}} has no TTL parameter; TTL is 
> fixed per cache/CacheManager configuration (e.g. the Caffeine spec or Redis 
> entry-ttl config), not per message. The State Store {{ttl}} endpoint option 
> and {{CamelStateStoreTtl}} header would have no effect against this backend; 
> the adapter should log a WARN if a TTL is supplied so the mismatch isn't 
> silent.
> * *Spring Boot only* - this lives in camel-spring-boot, not camel-api/core, 
> so it is not available to other runtimes (Quarkus, camel-main, etc.); those 
> keep using the existing backend-specific KeyValueRepository implementations.
> * *Cache pre-declaration* - some {{CacheManager}} implementations require 
> cache names to be pre-declared (e.g. via {{spring.cache.cache-names}}); a 
> {{storeName}} without a matching configured cache would fail to resolve, 
> unlike the always-available in-memory default.
> _Claude Code on behalf of Federico Mariani_



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to