[
https://issues.apache.org/jira/browse/CAMEL-11114?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18107456#comment-18107456
]
Guillaume Nodet commented on CAMEL-11114:
-----------------------------------------
{noformat:title=AI-generated content}
_Claude Code on behalf of gnodet_
{noformat}
h2. Implementation Proposal: Cache EIP leveraging camel-state-store (PR #22158)
This is a detailed implementation proposal for CAMEL-11114, designed to
leverage the new {{camel-state-store}} component from
[CAMEL-23239|https://issues.apache.org/jira/browse/CAMEL-23239] ([PR
#22158|https://github.com/apache/camel/pull/22158]).
h3. Architecture Decision: New {{CacheRepository}} SPI (following
{{IdempotentRepository}} pattern)
Define a minimal SPI in {{camel-api}} with an in-memory default in
{{camel-support}}, and let {{camel-state-store}} provide an adapter for its
pluggable backends.
||Pattern||SPI in core||Default impl||Extra dep needed?||
|IdempotentConsumer|{{IdempotentRepository}} in
{{camel-api}}|{{MemoryIdempotentRepository}}|No|
|CircuitBreaker|None|None|Yes (resilience4j)|
|*Cache EIP (proposed)*|{{CacheRepository}} in
{{camel-api}}|{{MemoryCacheRepository}}|No|
*Why not move {{StateStoreBackend}} to core:* {{StateStoreBackend}} has methods
the Cache EIP doesn't need ({{putIfAbsent}}, {{delete}}, {{keys}}, {{size}}) —
it's a general-purpose store, not a cache SPI. The adapter pattern keeps
{{camel-state-store}} optional.
h3. Proposed DSL Syntax
*Java DSL:*
{code:java}
// Minimal — auto-creates MemoryCacheRepository
from("direct:start")
.cache(simple("${header.productId}"))
.to("http://expensive-service")
.unmarshal().json()
.end()
.to("direct:continue");
// With options
from("direct:start")
.cache(simple("${header.productId}"))
.ttl("10m")
.cacheRepository("myRedisCache")
.to("http://expensive-service")
.end();
// Expression clause form
from("direct:start")
.cache().simple("${header.productId}")
.ttl(600000)
.to("http://expensive-service")
.end();
{code}
*XML DSL:*
{code:xml}
<cache cacheRepository="myCache" ttl="10m">
<simple>${header.productId}</simple>
<to uri="http://expensive-service"/>
</cache>
{code}
*YAML DSL:*
{code:yaml}
- cache:
simple: "${header.productId}"
cacheRepository: "myCache"
ttl: "10m"
steps:
- to: "http://expensive-service"
{code}
_Note:_ The original 2017 proposal used
{{.cache().on(expr).ttl(n)...endCache()}}. The proposed syntax is more
idiomatic for modern Camel — {{cache(expression)}} follows the
{{idempotentConsumer(expression)}} pattern, and {{.end()}} follows the current
convention.
h3. CacheRepository SPI ({{camel-api}})
{code:java}
/**
* Repository for storing cached values, used by the Cache EIP.
* @since 4.23
*/
public interface CacheRepository extends Service {
Object get(String key);
void put(String key, Object value);
void put(String key, Object value, long ttlMillis);
boolean contains(String key);
void clear();
}
{code}
Plus a {{CamelCacheHit}} exchange property (boolean) so downstream processors
can inspect whether the body came from cache.
h3. Key Components
*{{CacheDefinition}}* ({{camel-core-model}}):
* Extends {{OutputExpressionNode}} (same base as
{{IdempotentConsumerDefinition}} — provides both key expression and child
outputs)
* Fields: {{cacheRepository}} (registry ref), {{cacheRepositoryBean}}
(programmatic), {{ttl}} (duration string), {{cacheNull}} (boolean)
*{{MemoryCacheRepository}}* ({{camel-support}}):
* Default in-memory implementation using {{ConcurrentHashMap}} with lazy TTL
expiry
* Configurable {{defaultTtlMillis}} and {{maximumSize}} (default 1000, FIFO
eviction)
*{{CacheProcessor}}* ({{camel-core-processor}}):
* Extends {{AsyncProcessorSupport}}
* Runtime flow:
** Evaluate key expression → if null, execute child unconditionally
** Try {{cacheRepository.get(key)}} → if HIT, set body from cache, set
{{CamelCacheHit=true}}, skip child
** If MISS, execute child processor → on success, {{cacheRepository.put(key,
body, ttl)}}
** Cache exceptions are logged but *never* propagate to the exchange (graceful
degradation)
** Failed exchanges are never cached
*{{CacheReifier}}* ({{camel-core-reifier}}):
* Extends {{ExpressionReifier<CacheDefinition>}} (same as
{{IdempotentConsumerReifier}})
* If no {{CacheRepository}} configured, auto-creates {{MemoryCacheRepository}}
— zero-config experience
h3. Integration with camel-state-store (separate follow-up PR after #22158
merges)
A thin adapter {{StateStoreBackendCacheRepository}} wraps any
{{StateStoreBackend}} as a {{CacheRepository}}:
{code:java}
@BindToRegistry("productCache")
public CacheRepository productCache() {
RedisStateStoreBackend redis = new RedisStateStoreBackend();
redis.setRedisUrl("redis://localhost:6379");
return new StateStoreBackendCacheRepository(redis);
}
{code}
This means users who want Caffeine/Redis/Infinispan-backed caching add the
{{camel-state-store}} dependency and register the adapter.
h3. File Inventory (16 files + generated)
||#||File||Action||
|1|{{core/camel-api/.../spi/CacheRepository.java}}|CREATE|
|2|{{core/camel-api/.../Exchange.java}}|MODIFY (add {{CACHE_HIT}})|
|3|{{core/camel-api/.../ExchangePropertyKey.java}}|MODIFY|
|4|{{core/camel-support/.../cache/MemoryCacheRepository.java}}|CREATE|
|5|{{core/camel-core-model/.../model/CacheDefinition.java}}|CREATE|
|6|{{core/camel-core-model/.../model/ProcessorDefinition.java}}|MODIFY (add
{{cache()}} methods)|
|7|{{core/camel-core-processor/.../processor/CacheProcessor.java}}|CREATE|
|8|{{core/camel-core-reifier/.../reifier/CacheReifier.java}}|CREATE|
|9|{{core/camel-core-reifier/.../reifier/ProcessorReifier.java}}|MODIFY|
|10-12|Unit tests, integration tests, YAML tests|CREATE|
|13-15|EIP doc page, nav entry, upgrade guide|CREATE/MODIFY|
|16|{{camel-state-store/.../StateStoreBackendCacheRepository.java}}|CREATE
(separate PR)|
h3. Items for Discussion
# *Naming:* {{CacheRepository}} vs {{CacheStore}} vs {{CacheProvider}}.
Recommendation: {{CacheRepository}} for consistency with
{{IdempotentRepository}}, {{AggregationRepository}}.
# *Default behavior:* Should {{cache()}} without an explicit repository
auto-create an in-memory one? Recommendation: yes (matches JCachePolicy
auto-creation behavior).
# *Scope:* Body-only caching for v1 (matching JCachePolicy behavior), with
potential headers/exchange extension later.
# *Relationship to JCachePolicy:* The existing {{camel-jcache}}
{{JCachePolicy}} (CAMEL-13119) uses the {{Policy}} API and is tied to
{{javax.cache}}. The Cache EIP is a first-class DSL element that works with any
{{CacheRepository}} implementation. Both can coexist; the Cache EIP supersedes
the policy-based approach for new development.
> Create cache DSL
> ----------------
>
> Key: CAMEL-11114
> URL: https://issues.apache.org/jira/browse/CAMEL-11114
> Project: Camel
> Issue Type: New Feature
> Components: camel-core, eip
> Reporter: Nicola Ferraro
> Priority: Major
> Fix For: Future
>
>
> We should evaluate adding a new "cache" dsl that can be used with all cache
> components in Camel. A default implementation may use also caffeine, included
> in camel-core.
> A possible usage example may be:
> {code}
> from("xxx")
> .cache().on("${header.yyy}").ttl(600000) // caches the body
>
> .to("http4://a-service-that-makes-me-pay-for-each-request.com/api/expensive-endpoint")
> .transform().zzz()
>
> .to("http4://or-a-service-that-i-can-call-few-times-a-day.com/api/limited-endpoint")
> .unmarshal()
> .endCache()
> {code}
> It should be also useful to protect internal services when using Camel e.g.
> as a api-gateway (almost what hystrix does in case of failure of the target
> host).
--
This message was sent by Atlassian Jira
(v8.20.10#820010)