Cache-aside caching in getByIds
Add caching to ItemService::getByIds. The cache and client are injected. On a call, serve what is already cached and fetch only the missing ids, then store the freshly fetched items so the next call hits the cache.
<?php
class ItemService {
public function __construct(
private Client $client,
private Cache $cache,
) {}
public function getByIds(array $ids): array {
// your code here
}
}
Write the implementation.
Apply the cache-aside (read-through) pattern. Read the cache for $ids, compute the misses with array_diff($ids, array_keys($cached)), return early if none, otherwise fetch the missing ids from the client, write them back to the cache, and return array_merge($cached, $fetched). The injected Cache and Client make the method testable.
- ✗Diffing ids against cached values instead of
array_keys($cached), so hits are never excluded - ✗Overwriting the cache with only the fetched items, dropping previously cached entries
- ✗Fetching the full id list on every call, defeating the point of the cache
- →How would you record cache hit/miss metrics without changing the method's return value?
- →What changes if the cache returns a partial set keyed by id versus a flat list?
The cache-aside pattern: the code reads the cache itself, on a miss goes to the source, and writes the result back itself. The key detail is that the missing ids are computed against the cache keys (array_keys($cached)), not its values.
<?php
public function getByIds(array $ids): array {
$cached = $this->cache->get($ids);
$missingIds = array_diff($ids, array_keys($cached));
if (empty($missingIds)) {
return $cached;
}
$fetched = $this->client->get($missingIds);
$this->cache->set($fetched);
return array_merge($cached, $fetched);
}
Cache and Client arrive via the constructor (DI), so a test can replace them with mocks and assert the client is called only for the misses. Hit/miss metrics fall out naturally from the size of $cached versus $missingIds.