Caching with decorators

As well as working with different versions of an API, I was able to use the same technique I wrote about yesterday to easily add a cacheable version of the API client.

As they all implement the same ApiClientInterface, I can inject and decorate a client with another client, making one solely responsible for caching the result from the API whilst keeping the API interaction logic separate (aka the Decorator design pattern).

Here's an example based on the code I wrote:

final class CacheableApiClient implements ApiClientInterface {

/**
* The cache duration in seconds.
*/
private const CACHE_DURATION = 3600;

public function __construct(
private readonly ApiClientInterface $client,
private readonly TimeInterface $time,
private readonly CacheBackendInterface $cache,
) {
}

public function getResults(): Collection {
$key = $this->getCacheKey();

$cache = $this->cache->get($key);

if ($cache !== FALSE) {
return $cache->data;
}

$result = $this->client->getResults();

$this->cache->set(
cid: $key,
data: $result,
expire: $this->time->getRequestTime() + self::CACHE_DURATION,
);

return $result;
}

}

Nothing in this instance is specific to either version of the API.

This client is only concerned with retrieving and saving cache data, and delegating any other logic to the original version.

With this approach, I can switch between V1ApiClient, V2ApiClient or any other version with the same methods without having to reimplement caching as that's handled within the CacheableApiClient.

But what if I don't want to interact with the API at all?

For local development, I have a FakeApiClient that returns a static response that I can work with.

The possibilities are endless.