Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Concevoir des applications PHP résilientes en 2019

Concevoir des applications PHP résilientes en 2019

Vous souhaitez créer une architecture en micro services, ou une API REST et un joli front en React parce que c'est à la mode. Mais que se passe-t-il quand l'un de vos micro services tombe, ou si un de vos appels à votre API met un peu trop de temps à répondre ?

En gros, comment gérez-vous vous la résilience de vos applications ?

Je vous montrerai comment faire : notamment à l'aide d'une librairie PHP que j'ai créée et qui est intégrée dans le projet Open Source PrestaShop.

Vous comprendrez comment fonctionne un "Circuit Breaker", comment le configurer et comment le tester.

A la fin de cette session, vous aurez les clés pour ne plus craindre les problèmes de réseau. Et vous aurez acquis un nouveau réflexe: penser au pire, pour garantir le minimum vital à vos utilisateurs.

Mickaël Andrieu

October 24, 2019
Tweet

More Decks by Mickaël Andrieu

Other Decks in Programming

Transcript

  1. Au programme Pourquoi c’est important Le design pattern Circuit Breaker

    Mise en application en PHP Applaudissements et/ou huées du public
  2. PrestaShop en 2019 Le parc de boutiques d’un leader mondial

    de l’e-commerce en bref ! 4 300K Boutiques 30% 65 Part de marché (FR) Pays
  3. Des services tiers partout • Pour les mises à jour

    • Système de news intégré • Modules tiers (Google analytics par ex) 6
  4. Les conséquences de l’indisponibilité • Perte d’image, de confiance •

    Perte d’argent (car marketplace intégrée) • Difficultés à résoudre le problème de façon pérenne 11
  5. Améliorer son infrastructure • GCP for the H*** ! •

    CDN & compagnie • Traefic & compagnie 14 “Google ça peut pas tomber façon” - un(e) dev.
  6. Améliorer son infrastructure • GCP for the H*** ! •

    CDN & compagnie • Traefic & compagnie 15 “Google ça peut pas tomber façon” - un(e) dev. On délègue cette dette technique à des tiers
  7. Améliorer son code • Requête avec client HTTP qui gère

    un timeout • Faire du code non bloquant • Prévoir un fallback en cas de problème ? 16 “En fait si, même les services de Google tombent parfois !” - un(e) autre dev, un peu chiant(e)
  8. Aller à un rendez vous 1. Taper à la porte

    2. Attendre une réponse 3. Insister ? 4. Repartir, s’assoir ? (réponse dégradée) 19
  9. Cas idéal: service disponible 1. On tape à la porte

    2. La personne nous ouvre 21 Dans ce cas, le circuit breaker laisse passer les requêtes. On dit qu’il est fermé et que le système est ouvert.
  10. Cas 2: service indisponible 1. On tape à la porte

    2. La personne nous ouvre pas 22 Passé un certain nombre de tentatives, on va appliquer un comportement dégradé. Le Circuit Breaker s’ouvre et intercepte toutes les requêtes au service tiers et retourne une réponse que l’on aura défini. On dit qu’il est ouvert et que le système est fermé ou isolé.
  11. Cas 3: vérification de disponibilité “Après avoir attendu un certain

    temps, on retape à la porte” 23 Passé un seuil défini, le Circuit Breaker va autoriser une requête sur le service tiers. • Si elle réussit, le Circuit se referme et le système est ouvert. • Si elle échoue le Circuit se ré ouvre et le système est isolé de l’extérieur. Cet état de transition est dit semi-ouvert ou semi-fermé.
  12. Le Circuit Breaker • Simple à comprendre et utiliser •

    Implémente les patterns Retry et Timeout • Permet de retourner une réponse dégradée 25 “Je vous l’avais dit, lol !” - un(e) autre dev
  13. Comment choisir ? Ganesha Circuit Breaker Resiliency PHP 5.6 5.6

    7.2 Guzzle ? v6 (optionel) v5 V6 (optionel) Tests ? 91% 95% 99% PHPStan ? 0 (avec erreurs) 7 7 28
  14. Ex d’utilisation de Resiliency 29 <?php $circuitBreaker = new MainCircuitBreaker($mainSystem,

    $storage, $dispatcher); /** * @var Service $service */ $fallbackResponse = function ($service) { return '{}'; }; $circuitBreaker->call( 'https://api.domain.com', $fallbackResponse, [ '_token' => '123456789', ] );
  15. Les ingrédients d’un bon Circuit Breaker • Un client HTTP

    • Un système de stockage persistant • Un objet qui définit la configuration • Un Event Dispatcher (“Nice to have”) 30
  16. Ex d’utilisation de Resiliency 31 <?php $client = new GuzzleClient([

    'proxy' => '192.168.16.1:10', 'method' => 'POST', ]); $mainSystem = MainSystem::createFromArray([ 'failures' => 2, 'timeout' => 0.1, 'stripped_timeout' => 0.2, 'threshold' => 10.0, ], $client); $storage = new SimpleArray(); // any PSR-14 Event Dispatcher implementation. $dispatcher = new Symfony\Component\EventDispatcher\EventDispatcher;
  17. Ex dans une application Symfony 4 32 class DemoController extends

    AbstractController { /** * @Route("/", name="home") * * @param CircuitBreaker $circuitBreaker */ public function index(CircuitBreaker $circuitBreaker) { $responseFromCircuitBreaker = $circuitBreaker->call( 'https://my-json-server.typicode.com/typicode/demo/comments', function () { return json_encode([ ['id' => 1, 'body' => 'Response from fallback', 'postId' => 1] ]); } ); } https://github.com/mickaelandrieu/resiliency-demo
  18. Tester correctement son système • Un client de test HTTP

    • Mocker les réponses attendues • Vérifier l’état du Circuit Breaker • … et la réponse reçue ! 33
  19. Ex de tests avec PHPUnit 34 <?php /** * Returns

    an instance of Client able to emulate * available and not available services. * * @return GuzzleClient */ protected function getTestClient(): GuzzleClient { $mock = new MockHandler([ new RequestException('Service unavailable', new Request('GET', 'test')), new RequestException('Service unavailable', new Request('GET', 'test')), new Response(200, [], '{"hello": "world"}'), ]); $handler = HandlerStack::create($mock); return new GuzzleClient(['handler' => $handler]); }
  20. Ex de tests avec PHPUnit 35 public function testCircuitBreakerWillBeOpenInCaseOfFailures(): void

    { $this->assertInstanceOf(Closed::class, $this->circuitBreaker->getState()); $response = $this->circuitBreaker->call( 'https://httpbin.org/get/foo', $this->createFallbackResponse() ); $this->assertSame('{"uri": https://httpbin.org/get/foo"}', $response); //After 1 failed call switch to OPENED state $this->assertInstanceOf(Opened::class, $this->circuitBreaker->getState()); $this->assertSame( '{"uri": https://httpbin.org/get/foo"}', $this->circuitBreaker->call( 'https://httpbin.org/get/foo', $this->createFallbackResponse() ) ); }