Capita di voler utilizzare Redis per memorizzare informazioni non fondamentali o che possono essere rigenerate anche nel caso non fossero accessibili.
In questi casi se il server Redis dovesse non essere disponibile per un qualsiasi problema, il nostro servizio sarebbe completamente interrotto pur essendo funzionanti tutti i sistemi necessari ad erogarlo.
Questo perché un errore di connessione al server Redis utilizzando SncRedisBundle genera un’eccezione in fase di bootstrap di Symfony impedendo la generazione di qualsiasi pagina.
In questi casi è possibile definire un client “opzionale” che non generi un errore nel caso di risulti impossibile connettersi al server Redis ma che, rilevata tale condizione, la notifichi al codice dello strato superiore permettendo di gestire un fallback senza interrompere il servizio.
Per far ciò basta definire una classe wrapper (decorator) per il client, che catturi un’eventuale eccezione in fase di connessione ed esponga tramite un flag lo stato della connessione stessa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
class OptionalRedis extends \Redis { private $disconnected = false; public function isConnected(): bool { return !$this->disconnected; } public function connect($host, $port = 6379, $timeout = 0.0, $reserved = null, $retry_interval = 0) { try { parent::connect($host, $port, $timeout, null, $retry_interval); } catch (\RedisException $e) { $this->disconnected = true; } } public function pconnect($host, $port = 6379, $timeout = 0.0, $persistent_id = null) { try { parent::pconnect($host, $port, $timeout, $persistent_id); } catch (\RedisException $e) { $this->disconnected = true; } } public function select($dbindex) { if ($this->disconnected) { return; } parent::select($dbindex); } public function setOption($name, $value) { if ($this->disconnected) { return; } parent::setOption($name, $value); } } |
Fatto questo possiamo, tramite un CompilerPass, sostituire la classe del client con quella appena definita in tutti i servizi taggati come “snc_redis.client”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class OptionalRedisClientsCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { $this->processDefinitions($container); } private function processDefinitions(ContainerBuilder $container): void { $clientIds = $container->findTaggedServiceIds('snc_redis.client'); foreach (array_keys($clientIds) as $clientId) { $clientDef = $container->getDefinition($clientId); $clientDef->setClass(OptionalRedis::class); } } } |
Aggiungiamo quindi il nostro nuovo CompilerPass al metodo build() del nostro bundle o del kernel per Symfony 4+:
1 2 3 |
$container->addCompilerPass(new OptionalRedisClientsCompilerPass()); |
Adesso anche quando il server Redis non è attivo Symfony potrà fare il bootstrap correttamente e nell’utilizzo del nostro client potremo far affidamento sul flag isConnected() per comportarci di conseguenza.
Ad esempio:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
... public function __construct(OptionalRedis $redisClient) { $this->redisClient = $redisClient; } public function getValore(string $key) { if (!$this->redisClient->isConnected()) { return $this->calcolaValore($key); } $valore = $this->redisClient->get($key); if(empty($valore) { $valore = $this->calcolaValore($key); $this->redisClient->set($key, $valore); } return $valore; } ... |