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

Concevoir des applications résilientes en 2019

Concevoir des applications résilientes en 2019

Que se passe-t-il dans vos applications si l'un de vos services tiers est indisponible ?

Chez PrestaShop, ça nous est arrivé fin 2017 et les conséquences ont été dramatiques !
Nous avons conçu une librarie PHP capable de vous protéger de l'indisponibilités des API dont vous dépendez mais il vous reste le plus dur à faire: sensibiliser vos équipes business et produit au "pire", pour prévoir la meilleure expérience possible vos utilisateurs

Mickaël Andrieu

November 20, 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. 4

  3. PrestaShop en 2019 Le parc de boutiques d’un leader mondial

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

    • Système de news intégré • Modules tiers (Google analytics par ex) 8
  5. 9

  6. 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 12
  7. 13

  8. Améliorer son infrastructure • GCP for the H*** ! •

    CDN & compagnie • Traefic & compagnie 15 “Google ça peut pas tomber façon” - un(e) dev.
  9. Disponibilité d’un service (SLA) 16 Disponibilité en % Indisponibilité par

    année 99 3.65 jours 99.9 8.76 heures 99.99 52.56 minutes 99.999 5.26 minutes 99.9999 31.5 secondes
  10. Durabilité d’un service 17 La durabilité d’un service est la

    garantie de conservation des données sur la durée. => On en parle ici <=
  11. Améliorer son infrastructure • GCP for the H*** ! •

    CDN & compagnie • Traefic & compagnie 18 “Google ça peut pas tomber façon” - un(e) dev. On délègue cette dette technique à des tiers
  12. 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 ? 19 “En fait si, même les services de Google tombent parfois !” - un(e) autre dev, un peu chiant(e)
  13. La porte est fermée, que faire ? 1. Taper à

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

    2. La personne nous ouvre 26 Le Circuit Breaker laisse passer les requêtes. On dit qu’il est fermé et que le système est ouvert.
  15. Cas 2: service indisponible 1. On tape à la porte

    2. Personne ne répond 27 Passé un certain nombre de tentatives, on va appliquer un comportement dégradé.
  16. Cas 2: service indisponible 1. On tape à la porte

    2. La personne ne nous ouvre pas 28 Le Circuit Breaker s’ouvre et intercepte toutes les requêtes. Il retourne une réponse dégradée. On dit qu’il est ouvert et que le système est fermé ou isolé.
  17. Cas 3: vérification de disponibilité “Après avoir attendu un certain

    temps, on retape à la porte” 29 Passé un certain délai, le Circuit Breaker va autoriser une requête sur le service tiers.
  18. Cas 3: vérification de disponibilité “Après avoir attendu un certain

    temps, on retape à la porte” 30 ➔ En cas de succès: le Circuit se referme et le système est ouvert. ➔ En cas d’échec: 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é.
  19. Le Circuit Breaker • Difficile à comprendre • Simple à

    utiliser • Implémente les patterns Retry et Timeout • Permet de retourner une réponse dégradée 32 “Lourd !” - un(e) autre dev
  20. Comment choisir ? Ganesha Circuit Breaker Resiliency 5.6 5.6 7.2

    v6 (optionnel) v5 V6 (optionnel) 91% 95% 99% 0 (avec erreurs) 7 7 35
  21. Les ingrédients d’un bon Circuit Breaker 1. Un client HTTP

    2. Un système de stockage persistant 3. Un objet qui définit la configuration 4. Un dispatcher d’événements 37
  22. Client(s) HTTP ❖ Capable de faire un appel HTTP(S) ❖

    Capable de gérer un timeout ❖ Compatible avec Guzzle 6 ✔ ❖ Compatible avec Symfony HttpClient ✔ 38
  23. Persistance du Circuit - adapters 40 ➔ Tableau PHP ➔

    Fichiers ➔ Doctrine ORM ➔ Memcache(d) ➔ Redis ... https://symfony.com/doc/current/components/cache.html#available-cache-adapters
  24. Configuration du Circuit Breaker 41 • Nombre d’échecs (failures) •

    Temps d’attente (timeout) du Client HTTP • Seuil d’attente avant test de disponibilité (threshold) • Temps d’attente secondaire (stripped timeout)
  25. Les événements disponibles 43 ★ Initiated ★ Tried ★ Opened

    ★ ReOpened ★ Closed ★ AvailabilityChecked / “HalfOpened” ★ Reseted ★ Isolated
  26. Préparation du Circuit Breaker 44 <?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(); // une implémentation PSR-14 de l’Event Dispatcher. $dispatcher = new Symfony\Component\EventDispatcher\EventDispatcher();
  27. Utilisation de Resiliency 45 <?php $circuitBreaker = new MainCircuitBreaker($mainSystem, $storage,

    $dispatcher); $fallbackResponse = function ($request) { $uri = $request->getUri(); return "{'error': 'le service API ('. $uri .') est actuellement indisponible'}"; }; $circuitBreaker->call( 'https://api.domain.com', $fallbackResponse, [ '_token' => '123456789', ] );
  28. Utilisation de Resiliency - Isolation 46 <?php $circuitBreaker = new

    MainCircuitBreaker(...); /** * Isoler le système d’un service tiers */ $circuitBreaker->isolate('https://api.domain.com'); /** * Une fois la maintenance terminée, on ré-ouvre le système à l’extérieur ! */ $circuitBreaker->reset('https://api.domain.com');
  29. Utilisation de Resiliency - Monitoring 47 <?php $monitor = new

    SimpleMonitor(); /** * Collecte d’informations lors du cycle de vie de l’application */ function listener(Event $event) { $monitor->add($event); }; /** * Récupération d’un rapport complet pour analyse/stockage */ $report = $monitor->getReport();
  30. Ex dans une application Symfony 48 class DemoController extends AbstractController

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

    2. Mocker les réponses attendues 3. Vérifier l’état du Circuit Breaker 4. … et le contenu de la réponse reçue ! 51
  32. Ex de tests avec PHPUnit 52 <?php /** * Retourne

    une instance de Client HTTP capable de simuler * la disponibilité ou l’indisponibilité de services tiers. * * @return GuzzleClient */ protected function getTestClient(): GuzzleClient { $mock = new MockHandler([ new RequestException('Service indisponible', new Request('GET', 'test')), new RequestException('Service indisponible', new Request('GET', 'test')), new Response(200, [], '{"bonjour": "Codeurs et Codeuses en Seine"}'), ]); $handler = HandlerStack::create($mock); return new GuzzleClient(['handler' => $handler]); }
  33. Ex de tests avec PHPUnit 53 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); //Après 1 échec le Circuit Breaker s’ouvre ! $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() ) ); }