Method response caching

← Method response caching

Ashterix

docs: rewrite JsonRpcBundle docs, add SDK docs, move them under docs/api

en/docs/api/json-rpc-bundle/add_rpc_service/cache.md new file +193 −0

Summary

Classname

Cache

Namespace

Ufo\RpcObject\RPC

Target

method

Constructor

/**
 * Cache constructor.
 *
 * @param int $lifetimeSecond
 * @param array $environments
 */
public function __construct(
    public int $lifetimeSecond = self::T_MINUTE,
    public array $environments = [self::ENV_PROD],
) {}

Method response caching

By default, response caching for your RPC methods is disabled, and every time you call the API the method is executed.

However, for certain methods whose result rarely changes, it sometimes makes sense to enable response caching to speed up request processing, preserve rate limits, reduce load on parts of the system, etc.

Responses are stored in a separate cache pool of the bundle (rpc.cache.pool, filesystem-based in %kernel.cache_dir%). It can be replaced with any application pool, for example a shared Redis one, via cache.pool in the configuration.

Enabling caching is as simple as possible: all you need to do is add the #[RPC\Cache] attribute to the method.

namespace App\Api\Procedures;  
  
use Ufo\RpcObject\RPC;  
  
class SomeApiMethod implements IRpcService  
{  
    #[RPC\Cache]  
    public function methodThatWorksForALongTime(): void {}  
}

This configuration caches the response for one minute; every subsequent request to this method with the same parameters gets the response instantly from the cache.

The cache key consists of the method implementation (Class::method), the API version and the sorted request parameters, so the order of keys in nested objects does not affect the key. Methods without #[RPC\Cache] do not access the cache at all.

As usual, you can clear the cache by running the Symfony command

bin/console cache:clear

If needed, you can increase the cache lifetime by passing the number of seconds in the lifetimeSecond parameter. For convenience, there are constants that cover most needs, but you can also pass the value as a plain number.

use Ufo\RpcObject\RPC\Cache;  
  
Cache::T_MINUTE; // 60  
Cache::T_5_MINUTES; // 300  
Cache::T_10_MINUTES; // 600  
Cache::T_30_MINUTES; // 1800  
Cache::T_HOUR; // 3600  
Cache::T_2_HOURS; // 7200  
Cache::T_5_HOURS; // 18000  
Cache::T_10_HOURS; // 36000  
Cache::T_DAY; // 86400  
Cache::T_2_DAYS; // 172800  
Cache::T_3_DAYS; // 259200  
Cache::T_5_DAYS; // 432000  
Cache::T_10_DAYS; // 864000  
Cache::T_WEEK; // 604800  
Cache::T_2_WEEKS; // 1209600  
Cache::T_3_WEEKS; // 1814400  
Cache::T_MONTH; // 2592000  
Cache::T_2_MONTHS; // 5184000  
Cache::T_3_MONTHS; // 7776000  
Cache::T_HALF_YEAR; // 15724800  
Cache::T_YEAR; // 31536000

You can also change the environments in which caching can be enabled. By default, caching works only in the prod environment. But you can change this by passing an array of environment names in which you want to cache results. In the example below, the result is cached for one day in the prod and dev environments.

namespace App\Api\Procedures;  
  
use Ufo\RpcObject\RPC;  
  
class SomeApiMethod implements IRpcService  
{  
    #[RPC\Cache(lifetimeSecond: RPC\Cache::T_DAY, environments: ['dev', 'prod'])]  
    public function methodThatWorksForALongTime(): void {}  
}

Bypassing the cache for a single request

The special parameter $rpc.ignoreCache forces the response to be computed again and overwritten in the cache:

{"jsonrpc": "2.0", "id": 1, "method": "SomeApiMethod.methodThatWorksForALongTime", "params": {"$rpc": {"ignoreCache": true}}}

#[RPC\CacheRelation]

A method that changes data can clear the cached responses of related methods and, if needed, warm them up again right away.

public function __construct(
    public ?string $serviceFQCN = null,  // class of the related methods; defaults to the current one
    public array $methods = [],          // names of the related methods
    public bool $warmUp = false,         // run them again right away to fill the cache
)
use Ufo\RpcObject\RPC;

#[RPC\Info('User')]
final class UserProcedure implements IRpcService
{
    #[RPC\Cache(lifetimeSecond: RPC\Cache::T_HOUR)]
    public function get(int $id): UserDto { /* ... */ }

    #[RPC\CacheRelation(methods: ['get'], warmUp: true)]
    public function rename(int $id, string $name): bool { /* ... */ }
}

After a successful User.rename {"id": 5, ...}, the cached User.get {"id": 5} is deleted and rebuilt. The key of the related request is made of those parameters of the current request whose names match the parameters of the related method. The attribute can be specified multiple times.

Invalidation runs on the rpc.post_response event, i.e. after the response has been sent to the client. It does not run for an error response.

On multiple servers, configure a shared cache.pool — otherwise invalidation only takes effect on the server that handled the request.

Managing the cache from code

Ufo\JsonRpcBundle\Server\RpcCache\RpcCacheService provides the same capabilities programmatically:

Method What it does
deleteRelationCache(string $class, array $methods, array $params) Deletes cached responses of the specified methods
warmCache(string $class, array $methods, array $params, bool $deleteCache = true) Deletes and immediately rebuilds the cache
deleteRequestCache(RpcRequest $request) Deletes the response of a single request
public function __construct(private RpcCacheService $rpcCache) {}

// after updating an entity outside RPC
$this->rpcCache->deleteRelationCache(UserProcedure::class, ['get'], ['id' => $id]);