Flerex
Flerex

Reputation: 354

How to organize code to wrap around an API a cache system

I am building an application that uses an external API through an already made library. Let us imagine this external service provides weather information for a given place. We have a controller like this:

class WeatherController
{
    public function show($place, WeatherLibrary $api)
    {
        return $api->getWeatherFor($place);
    }
}

It looks okay, but this API has a requests per minute limit that creates the need of a caching system. I was thinking about using the native Cache API Laravel provides. But, to keep my code organized I want to avoid having the Cache part of the logic in my controllers like this:

use Illuminate\Support\Facades\Cache;

class WeatherController
{
    public function show($place, WeatherLibrary $api)
    {
        return Cache::get($place, function() use ($place, $api) {
            $result = $api->getWeatherFor($place);
            Cache::put($place, $result, 60);
            return $result;
        });
    }
}

What approach should I take to organize this? I was thinking about the repository pattern, but I'm not so sure if it's the correct way to do it, as repositories have, at least, CRUD-like operations, and this “repository” would have custom methods according to the external service business logic.

Upvotes: 0

Views: 324

Answers (1)

Travis Britz
Travis Britz

Reputation: 5552

Going from bishop's comment, you could create a proxy class like this:

class WeatherLibraryProxy
{
    /**
     * @var WeatherLibrary
     */
    protected $driver;

    public function __construct(WeatherLibrary $driver)
    {
        $this->driver = $driver;
    }


    /**
     * Catch all method calls and either serve results from the cache
     * or defer to the underlying api driver and cache the result
     */
    public function __call($method, $parameters)
    {
        $cache_key = $method . implode(',', $parameters);

        return cache()->remember(
            $cache_key,
            now()->addMinutes(60),
            function () use ($method, $parameters) {
                return $this->driver->$method(...$parameters);
        });
    }

}

Now any shared functionality (like checking remaining rate limits) can be put into your Proxy class, which you use everywhere in your app instead of the underlying WeatherLibrary class.

Then in your controller, change WeatherLibrary to WeatherLibraryProxy:

class WeatherController
{
    public function show($place, WeatherLibraryProxy $api)
    {
        return $api->getWeatherFor($place);
    }
}

Laravel's service container should automatically inject the WeatherLibrary to your proxy's constructor. If it doesn't, then you can instruct Laravel how to build a new instance in your AppServiceProvider.php:

$this->app->bind(WeatherLibrary::class, function ($app) {
    return new WeatherLibrary($arg1, $arg2, ...);
});

More on automatic injection: https://laravel.com/docs/6.0/container#automatic-injection

Upvotes: 2

Related Questions