agentsclimarketplace

Kora aop caching

Skill kora-projects/kora-skills/plugins/kora-v1/skills/kora-aop-caching

Declarative and imperative caching for Kora via compile-time AOP. Covers @Cacheable (read-through), @CachePut (write-through), @CacheInvalidate (evict / invalidateAll), the typed @Cache contract over CaffeineCache (artifact cache-caffeine, in-process) and RedisCache (artifact cache-redis, Lettuce-backed, distributed), CacheKeyMapper + @Mapping for composite/derived keys, the parameters key attribute, @Json on Redis value types, LoadableCache, and stacked annotations for multi-level L1/L2 caching. Use when adding caching to a Kora service, choosing Caffeine vs Redis, configuring a cache via @ConfigSource paths (maximumSize, expireAfterWrite, keyPrefix), fixing "required keyPrefix" graph build failures, or when aspects do not fire because a class is final/not open.From its SKILL.md

Install
npx -y skills add kora-projects/kora-skills --skill kora-aop-caching

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

9.6 KB, ~2.2k tokens by cl100k_base, as published. Nobody here has run it

Kora AOP Caching Skill

Focus: Declarative caching via compile-time AOP annotations for Caffeine (in-process) and Redis (distributed) caches.

Read this first when:

  • Adding cache to methods with @Cacheable, @CachePut, @CacheInvalidate
  • Configuring Caffeine or Redis cache backends
  • Creating custom cache key mappers with CacheKeyMapper
  • Setting up multi-level caching (L1 Caffeine + L2 Redis)

Quick Start

1. Add Dependencies

All Kora artifacts inherit their version from the kora-parent BOM, so never pin a version on a ru.tinkoff.kora:* dependency. The annotation processor is mandatory - without it the @Cache/@Cacheable aspects and the typed cache implementation are never generated.

dependencies {
    koraBom platform("ru.tinkoff.kora:kora-parent:$koraVersion")  // e.g. 1.2.17
    annotationProcessor "ru.tinkoff.kora:annotation-processors"   // Kotlin: ksp "ru.tinkoff.kora:symbol-processors"

    // Local cache (Caffeine) - recommended for most cases
    implementation "ru.tinkoff.kora:cache-caffeine"

    // Or distributed cache (Redis/Lettuce) - for multi-pod shared state
    // implementation "ru.tinkoff.kora:cache-redis"
}

2. Enable in Application

@KoraApp
public interface Application extends CaffeineCacheModule {}
// Or for Redis: extends RedisCacheModule
// Or for multi-level: extends CaffeineCacheModule, RedisCacheModule

3. Declare Typed Cache

@Cache("orders.cache.config")
public interface OrderCache extends CaffeineCache<UUID, OrderDto> {}

4. Use Cache Annotations

@Component
public class OrdersService {

    @Cacheable(OrderCache.class)
    public OrderDto get(UUID id) {
        return repository.find(id);
    }

    @CachePut(value = OrderCache.class, parameters = { "id" })
    public OrderDto update(UUID id, OrderDto dto) {
        return repository.save(id, dto);
    }

    @CacheInvalidate(OrderCache.class)
    public void delete(UUID id) {
        repository.delete(id);
    }
}

5. Add Configuration

# Caffeine
orders.cache.config {
  maximumSize = 10000
  expireAfterWrite = "10m"
}

# Redis (requires keyPrefix)
orders.cache.config {
  keyPrefix = "orders"
  expireAfterWrite = "1h"
}

Imperative Cache Usage

For programmatic cache usage (stateful caching, manual invalidation, rate limiting) without @Cacheable AOP, see Imperative Cache Reference.


Cache Annotations

AnnotationPurposeMethod RunsCache Behavior
@Cacheable(MyCache.class)Read-through cacheOn cache miss onlyLookup first; on miss call method, cache result
@CachePut(MyCache.class)Write-through cacheAlwaysCall method, then put result in cache
@CacheInvalidate(MyCache.class)Evict by keyAlwaysCall method, then evict key built from args
@CacheInvalidate(value = MyCache.class, invalidateAll = true)Clear entire cacheAlwaysCall method, then clear all entries

Important: Annotations are repeatable. Stack multiple @Cacheable for multi-level caching.


Key Strategies

Single-Argument Key

@Cacheable(OrderCache.class)
public OrderDto get(UUID id) { /* key = id */ }

Composite Key

@Cache("orders.cache")
public interface OrderCache extends CaffeineCache<OrderCache.Key, OrderDto> {
    record Key(UUID tenantId, UUID orderId) {}
}
@Cacheable(OrderCache.class)
public OrderDto get(UUID tenantId, UUID orderId) { /* key = new Key(tenantId, orderId) */ }

Custom Key Mapper

public static final class OrderContextMapper implements CacheKeyMapper<OrderCache.Key, OrderContext> {
    public OrderCache.Key map(OrderContext ctx) { return new OrderCache.Key(ctx.tenantId(), ctx.orderId()); }
}
@Cacheable(OrderCache.class)
@Mapping(OrderContextMapper.class)
public OrderDto getByContext(OrderContext ctx) { /* key = mapper.map(ctx) */ }

Subset/Reordering with parameters

@Cacheable(value = OrderCache.class, parameters = { "orderId", "tenantId" })
public OrderDto get(UUID tenantId, String extra, UUID orderId) { /* key = new Key(orderId, tenantId) */ }

See cache-key-mapper-reference.md for details.


Multi-Level Cache (Caffeine + Redis)

@KoraApp
public interface Application extends CaffeineCacheModule, RedisCacheModule {
    @Cache("orders.caffeine.config")
    interface OrderCaffeineCache extends CaffeineCache<UUID, OrderDto> {}

    @Cache("orders.redis.config")
    interface OrderRedisCache extends RedisCache<UUID, @Json OrderDto> {}
}

@Cacheable(OrderCaffeineCache.class)  // L1 first
@Cacheable(OrderRedisCache.class)     // L2 on miss
public OrderDto get(UUID id) { /* Caffeine → Redis → repository */ }

See multi-level-cache-reference.md for details.


Common Pitfalls

ProblemSolution
Wrong artifact nameUse cache-caffeine not caffeine-cache, cache-redis not lettuce-cache
Class is final (Java) / not open (Kotlin)AOP requires subclassing: non-final in Java, open in Kotlin
Redis keyPrefix missingkeyPrefix is required for Redis - graph build fails without it
Wrong argument orderArgument order must match record component order, or use parameters
Self-invocation bypassCall from another bean if cache not triggering on internal calls
Multi-level wrong orderStack annotations L1 first (Caffeine), then L2 (Redis)
Redis serializationUse @Json on value type: RedisCache<K, @Json V>

Imperative API

@Component
public class OrdersService {
    private final OrderCache cache;
    public OrdersService(OrderCache cache) { this.cache = cache; }

    public OrderDto getOrCreate(UUID id) {
        var cached = cache.get(id);
        if (cached != null) return cached;
        var loaded = repository.find(id);
        cache.put(id, loaded);
        return loaded;
    }
}

See cache-caffeine-reference.md for full API.


Testing

Use @KoraAppTest(Application.class) and inject both the service and the cache with @TestComponent (field injection by the Kora JUnit 5 extension - not @Inject). Reset the cache between tests via the imperative API.

@KoraAppTest(Application.class)
class OrdersServiceTest {

    @TestComponent
    private OrdersService service;
    @TestComponent
    private OrderCache cache;

    @BeforeEach
    void cleanup() {
        cache.invalidateAll();
    }

    @Test
    void cachesResultBetweenCalls() {
        var id = UUID.randomUUID();
        var first = service.get(id);
        var second = service.get(id);   // served from cache
        assertEquals(first, second);
    }
}

Add testImplementation "ru.tinkoff.kora:test-junit5". See kora-testing-junit-java for the full testing approach.


Reference Documents

DocumentDescription
cacheable-reference.md@Cacheable, @CachePut, @CacheInvalidate details
cache-key-mapper-reference.mdCacheKeyMapper, composite keys
cache-caffeine-reference.mdCaffeine configuration
cache-redis-reference.mdRedis/Lettuce configuration
multi-level-cache-reference.mdL1+L2 patterns

Common Pitfalls

SymptomFix
Cache aspect doesn't fireClass is final (Java) or not open (Kotlin) — AOP needs inheritance
"Required keyPrefix" graph build failure (Redis)Add keyPrefix to cache config section
Null value throws on put()Cache values must be @Nonnull
Need reverse lookup (by value)Maintain separate index cache
Want stateful cache (not memoization)Use imperative pattern (see Imperative Cache)

Assets

Templates: OrderCache.java.template, OrderCache.kt.template, CacheConfig.java.template, CacheConfig.kt.template

See assets/README.md.


Sources

  • Cache module documentation: .kora-agent/kora-docs/mkdocs/docs/en/documentation/cache.md
  • Cache guide: .kora-agent/kora-docs/mkdocs/docs/en/guides/cache.md
  • Multi-level cache guide: .kora-agent/kora-docs/mkdocs/docs/en/guides/cache-multi-level.md
  • Caffeine example: .kora-agent/kora-examples/examples/java/kora-java-cache-caffeine/
  • Redis example: .kora-agent/kora-examples/examples/java/kora-java-cache-redis/
  • Guide apps: .kora-agent/kora-examples/guides/java/kora-java-guide-cache-app/, .../kora-java-guide-cache-multi-level-app/

What ships with it: 11 files

80.5 KB alongside SKILL.md

evals/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.