Add caching: the Spring cache abstraction, keys, eviction timing and the self-invocation trap
A Spring Boot 4.1.1 module whose test suite is the evidence for the article: 19 tests producing 22 transcripts under docs/output/, plus 12 documentation chapters. Findings the build pins: - @EnableCaching has no exposeProxy attribute; the widely-copied @EnableCaching(exposeProxy = true) does not compile. - Two methods sharing a cache name and an argument type share a key space, and one silently serves the other's answers. - beforeInvocation = true is NOT deferred by TransactionAwareCacheManagerProxy on 7.0.9 - doEvict picks evictIfPresent, which the decorator does not intercept. - Four of five invalid declarations start a clean context and throw at the first call. - Caffeine on the classpath silently displaces the simple provider.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
[← diagnostics](11-diagnostics.md) · [README](../README.md)
|
||||
|
||||
# 12. Before this goes to production
|
||||
|
||||
## Should there be a cache here at all?
|
||||
|
||||
Be honest about the answer. A cache is a correctness liability you accept in exchange for
|
||||
latency. If the method is not measurably slow, or the hit rate will be low, or the data must be
|
||||
current, the right amount of caching is none. Half the caches in a typical codebase were added
|
||||
without a measurement and are never revisited.
|
||||
|
||||
## The checklist
|
||||
|
||||
**Configuration**
|
||||
|
||||
- [ ] `spring.cache.type` is set explicitly, so a new dependency cannot change the provider
|
||||
([chapter 8](08-providers-and-ttl.md))
|
||||
- [ ] `spring.cache.cache-names` declares every cache, so a typo fails loudly
|
||||
- [ ] Every mutable cache has a TTL. It bounds the damage from every other mistake on this list
|
||||
- [ ] Every cache has a size bound, or the data set is provably small
|
||||
- [ ] `@EnableCaching` is not on the main application class
|
||||
|
||||
**Correctness**
|
||||
|
||||
- [ ] One cache name per method, or an explicit `key` that includes the method
|
||||
([chapter 4](04-keys.md))
|
||||
- [ ] Keys are immutable and serialise to something stable
|
||||
- [ ] Cached values are DTOs, not JPA entities ([chapter 9](09-versus-hibernate-l2.md))
|
||||
- [ ] Cached values are immutable, or defensively copied — the map hands every caller the same
|
||||
instance
|
||||
- [ ] Mutating methods evict rather than put, with `beforeInvocation = true`
|
||||
([chapter 5](05-eviction.md))
|
||||
- [ ] Nothing relies on `this.cachedMethod(...)` ([chapter 3](03-self-invocation.md))
|
||||
- [ ] Every cached method is called at least once by a test — four of the five invalid
|
||||
declarations in [chapter 2](02-the-three-annotations.md) only fail at the first call
|
||||
|
||||
**Operations**
|
||||
|
||||
- [ ] Hit rate and eviction count are on a dashboard (`recordStats()` for Caffeine)
|
||||
- [ ] There is a way to clear a cache without a deployment (`DELETE /actuator/caches/{name}`)
|
||||
- [ ] A `CacheErrorHandler` decision has been made per cache, not inherited by accident
|
||||
([chapter 5](05-eviction.md))
|
||||
- [ ] The behaviour with `spring.cache.type=none` has been tried at least once
|
||||
|
||||
**Distributed caches only**
|
||||
|
||||
- [ ] Values are serializable and the format survives a rolling deployment — a changed DTO shape
|
||||
with old entries still in Redis fails on read, per instance, at whatever hour
|
||||
- [ ] `spring.cache.redis.key-prefix` keeps this application out of everyone else's keyspace
|
||||
- [ ] The failure mode when the cache is unreachable has been decided: degrade or fail
|
||||
- [ ] TTLs are short enough that a missed eviction self-corrects
|
||||
|
||||
## The one-line version
|
||||
|
||||
Give every cache a TTL, evict rather than put, cache DTOs, and set `spring.cache.type`. Those
|
||||
four cover most of what goes wrong.
|
||||
Reference in New Issue
Block a user