[ 
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)

Reply via email to