Files
Flash5/flash-extensions/flash-ext-cache-redis/docs
Zakaria El OrcheandClaude Opus 5 24bb10175d feat(ext-cache-core): add the caching contract and a Caffeine backend
Split the way flash-ext-data and flash-ext-view are: cache-core defines
Cache, CacheManager, CacheSpec and CacheStats and talks to nothing;
cache-caffeine implements them in process.

    users = require(CacheManager.class).build("users", spec -> spec
            .maxSize(10_000).ttl(Duration.ofMinutes(10)));

    return users.get(id, repo::findById);

get(key, loader) is the only shape most code needs and the only one that is
hard to get right: the loader runs once per key across concurrent callers
rather than each racing its own. A null result stores nothing, because caching
absence is a decision rather than a default.

build(name, spec) is idempotent per name, so two handlers wanting one cache get
one cache without coordinating who creates it. Disagreeing about the spec
throws rather than resolving to whichever handler initialised first, which is a
bug that only surfaces under load.

recordStats() is opt-in — counting is two atomic increments per lookup, and a
cache nobody measures should not pay for numbers nobody reads. Unmeasured
caches return CacheStats.DISABLED rather than zeroes that look like a cold
cache.

Caffeine rather than a hand-rolled LRU: for genuinely low traffic
ConcurrentHashMap::computeIfAbsent is one line and needs no module at all, and
this exists for when that stops being true. W-TinyLFU admission, striped
counters and amortised eviction are not a weekend's work, and getting them
wrong yields a cache slower than no cache. The adapter is deliberately thin —
every method delegates, adding no wrapper, copy or locking of its own.

Caches are dropped through FlashContext.onClose, so values do not outlive the
app holding them. Invisible with one app per process; immediate under test.

flash-ext-cache-redis is designed but not built, and has docs only — no module,
no pom, no source. An empty module that builds an empty jar is dead weight in
the reactor. The docs record what changes once the cache can fail: get() must
decide whether to fall through to the loader, values need a codec,
invalidateAll needs a key prefix that becomes wire contract, and eviction stats
stop meaning anything. Those are decisions that want a real second replica to
check them against.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 12:30:03 +00:00
..

flash-ext-cache-redis — planned

Not implemented. This directory holds the design so the decision is written down rather than rediscovered; there is deliberately no module, no pom and no source, because an empty module that builds an empty jar is dead weight in the reactor and in everyone's dependency tree.

Add it when there is a second replica that actually needs shared state.

What it would implement

CacheManager and Cache from flash-ext-cache-core, so switching backend is an install-line change:

.install(new RedisCacheExtension(RedisConfig.of("redis://localhost:6379")))

The part that is not a drop-in

flash-ext-cache-caffeine cannot fail. A networked cache can, and that changes the contract in ways an adapter cannot hide:

  • get(key, loader) can fail before reaching the loader. The honest default is to fall through to the loader and serve the value uncached, so Redis being down degrades throughput rather than taking the application with it. That has to be a decision, not an accident.
  • Values must be serialized. Caffeine stores references. A byte[] codec belongs in the spec, and the natural default is whatever flash-ext-jackson is already configured with.
  • invalidateAll() is not free. Against a shared keyspace it is either a scan or a key prefix per cache name. The prefix is the right answer, and it means cache names become part of the wire contract.
  • Stats are per-client, not per-cache. Hit rate stays meaningful; eviction count does not, because Redis evicts on its own policy.

Why it is not built yet

Nothing in the codebase has two replicas sharing cache state. Building it now would mean choosing a client library, a serialization format and a failure policy with no real usage to check them against — and the failure policy in particular is the kind of decision that is wrong until a production incident tells you otherwise.