A cache keeps previously computed or retrieved values under keys so they can be reused instead of obtained again. It helps when fetching a value is costly and an earlier result is still acceptable. Freshness matters: a stable fact may be safe to reuse, while an old exchange rate may mislead. section 7.0 section 7.6
Avoid repeated exchange-rate requests
A converter processing a file of amounts could otherwise make an identical HTTP request for every line. One proposed cache stores rates in a dated temporary file: fetch when the file is absent or too old, load it otherwise, and provide a flag to flush it. An in-memory cache could instead sit behind the converter’s rate-fetching interface, avoiding a network call when the rate is already known. section 6.6.1 section 6.5
For a reusable Go cache, start with a map from keys to values. A key must be comparable; a value can have any type. Initialize the map in a constructor before storing entries. The basic operations are Read, which reports whether a key was found; Upsert, which stores or replaces a value; and Delete, which removes one. Storing V rather than *V leaves callers free to choose a pointer type when they need one. section 7.1 section 7.1.3
Listing 7.2 cache.go
The constructor calls make so a Cache begins with a usable map. section 7.1.3
A working map is only the first version. Decide how the cache behaves when:
- Several goroutines use it: a sequential test does not establish thread safety. Test simultaneous calls, then protect shared state with a mutex. section 7.3 section 7.4.2
- A value becomes stale: give entries an expiration time and treat an expired entry as absent. section 7.4 section 7.4.2
- Entries accumulate: set a maximum size and choose an eviction policy. One implementation removes the oldest entry; replacing an entry makes it new again. Tracking insertion order requires extra state. section 7.4 section 7.4.2
Listing 7.20 cache.go: Upsert with the new helper functions
func (c *Cache[K, V]) Upsert(key K, value V) {
c.mu.Lock()
defer c.mu.Unlock()
_, alreadyPresent := c.data[key]
switch {
case alreadyPresent:
c.deleteKeyValue(key) #1
case len(c.data) == c.maxSize: #2
c.deleteKeyValue(c.chronologicalKeys[0])
}
c.addKeyValue(key, value) #3
}
Upsert shows the capacity rule in action: under a mutex, it removes an existing key or evicts the oldest key before adding the new entry. section 7.4.2





































