class: center, middle, inverse # Retour d'expérience ## reactive architecture ## micro-services ## Symfony ??? # Agenda 1. Contexte 1. Définitions 1. SOA / Micro-services / Right-sized service ? 1. Reactive Manifesto 1. Architect cheat sheet ## À trier * ESB * API Bundles * Nelmio * Bulle d'échanges * Migration continue --- class: branded, inverse name: about-me # Fabien Meurillon .left-column[ ![avatar](images/avatar.png) [@FabienM](http://twitter.com/FabienM) ![smile](images/smile-transparent.png) [@GroupeSmile](http://twitter.com/GroupeSmile) ] .right-column[ ### Développeur polyglotte ### Architecte ### Évangéliste SOA ### Smile Group ] ??? Stand Smile sur le salon. --- # Un peu de contexte ![auchan](images/arf.png) Retour d'expérience issu de **Auchan Retail France**. Back-office e-commerce : Deux applications **monolithiques**, very legacy, framework MVC *custom*. **38 ans** de dette technique SensioLabs Insight. ??? AECF : Auchan.fr, Auchan Traiteur, auchandirect, etc... En gros, équipe de 30 à 50 personnes -- ## Forum PHP 2015 .left-column[ ![Alexandre Salomé au Forum PHP](images/asal.jpg) ] .right-column[ « *Retour d’expérience sur la migration du Back-Office* », Alexandre Salomé ] ??? Proposer de visionner la conférence sur youtube Merci à Alexandre pour les slides de rappels --- background-image: url(images/back-office-1.png) --- background-image: url(images/back-office-2.png) ??? Découpage issu d'une réflexion / rationalisation des processus métiers. Migration probablement destinée à l'échec si ce n'est pas fait. --- class: center, middle, inverse # On parle de quoi alors ? --- name: definitions # Merci Wikipedia ## SOA > An architectural pattern in computer software design in which application > components provide services to other components via a communications protocol, > typically over a network. > **These principles are independent of any vendor, product or technology.** ??? Alexandre Astier style : > Pardon, pardon mille fois de revenir sur des notions un petit peu > élémentaires, ça me semblait quand même intéressant de rappeler quelques > notions de base notamment pour la suite de l'exposé qui est —vous vous en > doutez— nettement plus complexe. -- ## Micro-Services > In computing, microservices is a software architecture style in which complex > applications are composed of **small, independent processes** communicating > with each other using **language-agnostic APIs**. ??? Une architecture micro-service **est** une SOA dont les composants applicatifs sont indépendants et **petits**. Indépendant aussi bien en code applicatif qu'en **données**. --- # Ordre de grandeur ## 1 micro-service = -- * 1 composant applicatif (package composer) -- * 1 domaine métier (*Domain Driven Design*) ??? Rediriger vers les conf au sujet de DDD -- * probablement moins de 5 aggrégats exposés sur l'API ??? On parle depuis quelques temps de *right-sized services* -- .service[ .blue[Commandes] .blue[Catalogue] .blue[...] .green[Fournisseurs] .green[Stock] .green[...] ] -- .service[ .orange[Securité] .orange[Portail Web] .orange[...] ] --- name: reactive class: inverse, middle, center # Going reactive ??? TODO: Pourquoi ? Disclaimer: Buzzwords --- # The reactive manifesto http://www.reactivemanifesto.org/ ??? Version 2.0 datée de septembre 2014. Déclaration d'intentions uniquement, pas des guidelines d'implémentations. Peut être vu comme une *cheat sheet*. -- ## Responsive ??? The system responds in a timely manner if at all possible. -- ## Resilient ??? The system stays responsive in the face of failure. -- ## Elastic ??? The system stays responsive under varying workload. -- ## Message Driven ??? Reactive Systems rely on asynchronous message-passing to establish a boundary between components that ensures loose coupling --- name: iteration1 class: inverse, middle, center # On sait où on va... ## Let's migrate! --- # Par où commencer ? -- ## par les services métiers ? Oui, mais : * beaucoup d'enjeu * le legacy ne doit pas s'arrêter * besoin d'une forte implication du métier, beaucoup d'inertie -- ## par les briques techniques ? * on est autonomes * on peut avancer en parallèle du cadrage des applis métiers -- .center[ ### OK, on y va ! ] --- # Briques techniques ## ExchangeBundle Bundle Symfony pour gérer les échanges entre micro-services. * chaque application intègre le bundle * permet d'envoyer et recevoir des messages sur filesystem, SGBD ou MOM (RabbitMQ/ActiveMQ) * un *listener* planifié en cron sur chaque application se charge de consommer les messages en attente ??? Codé en quelques jours. Uniquement pour POC. Super, on a refait RabbitMQ --- # Fail `:-/` -- ### .fa.fa-check-square-o[] Message driven ??? ### Orienté message * orienté message avec traitement asynchrone -- ### .fa.fa-times[] Elastic ??? ### Elastic * Multi-threading compliqué en PHP -- ### .fa.fa-times[] Resilient ??? ### Resilient * Statut du *listener* difficile à monitorer * Fortement couplé à l'infrastructure (adresse du MOM directement dans l'appli) -- ### .fa.fa-times[] Responsive ??? ### Responsive * le système ne "répond" jamais -- ### .fa.fa-times[] Testable ??? ### Testable * Injecter un message sur un environnement nécessite un accès à son infrastructure -- ### .fa.fa-times[] Réutilisable ??? ### Réutilisable * Impossible de réutiliser le composant pour les appels synchrones --- class: middle, center, inverse ## Quand on tient un marteau, tous les problèmes ressemblent à des clous -- ### Corollaire : -- ## Quand on est développeur Symfony, toutes les solutions ressemblent à des bundles --- # Reposer le problème Nous avons un ensemble de petits composants applicatifs **indépendants**, communicants de manière synchrone et surtout **asynchrone** au travers du réseau. La plupart de ces composants seront des applications Symfony, mais **pas tous**. -- ## Les échanges ne sont pas de la responsabilité des applications Nul besoin de trouver une solution tout de suite, reportons les problèmes. * Quel format d'échange ? * Quel protocole de transport ? ??? Intégrer un bundle dans une application étend ses responsabilités, même si le code est maintenu ailleurs. Reporter un problème dans l'informatique : appliquer des standards, appliquer des design patterns. --- class: inverse, middle, center # Format d'échange : Design Pattern -- ## Le *Canonical Data Model* --- # Format d'échange : Canonical Data Model ### L'application propriétaire d'une donnée est responsable de son format d'échange Il s'agit du *modèle de données canonique* (CDC). -- C'est une représentation portable et standardisée d'un objet métier, idéalement soumise à un *contrat* (XSD, JSON Schema). -- Le CDC est modélisé en PHP, puis mappé en JSON et/ou XML (*JMSSerializer*). .pull-left[ ```php namespace Auchan\RefProduit\AppBundle\Model; class Product { private $id; private $label; } ``` ] .pull-right[ ```json { "id": "1", "label": "An Awesome Product" } ``` ] ??? Tout à fait possible avec Symfony Serializer Le CDC est lmodélisation métier "publique" de l'appli --- class: inverse, middle, center # Un standard pour le transport ??? On cherche un protocole standard * parallélisable & scalable * stateless * supportant l'asynchrone * gérant les erreurs * supporté nativement par PHP et Symfony -- ## HTTP --- # HTTP : protocole de transport Chaque application est responsable de ses données, celles-ci ne *devraient pas* être dupliquées d'une application à l'autre. .pull-left[ ### Application consommatrice .mermaid[sequenceDiagram Stock->>Réf. Produit: GET /products/1 ] ] .pull-right[ ### Client React .mermaid[sequenceDiagram App React->>Réf. Produit: POST /products App React->>Réf. Produit: DELETE /products/1 ] ] --- # HTTP : protocole de transport Il est possible qu'une application souhaite déclencher des actions lors d'un événement survenu dans un autre composant. .fa.fa-arrow-right[] Elle ne les recevra que sur sa propre API. .mermaid[sequenceDiagram Réf. Produit->>Stock: POST /products Réf. Produit->>Stock: DELETE /products/1 ] ??? Faire remarquer similitude --- class: inverse, middle, center # Une observation Les routes et les messages qui permettent de *notifier* un autre microservice sont les mêmes que celles utilisées localement par une application pour son API. -- # Factorisons ! --- # Here comes the *ApiBundle* .pull-left[ ## Première conception ```bash src └── AppBundle ├── Controller │ └── Api │ └── ProductController.php ├── Model │ └── Api │ └── Product.php └── Service └── ProductService.php ``` ] -- .pull-right[ ## Avec l'ApiBundle ```bash src ├── AppBundle │ └── Service │ └── ProductService.php └── ApiBundle ├── Controller │ └── ProductController.php ├── Client │ └── ProductService.php ├── Event │ └── ProductEvent.php ├── Model │ └── Product.php └── composer.json ``` ] --- layout: true # L'*ApiBundle* est un ambassadeur Il contient : --- .pull-left[ ```bash src ├── AppBundle │ └── Service │ └── ProductService.php └── ApiBundle ├── Controller │ └── ProductController.php ├── Client │ └── ProductService.php ├── Event │ └── ProductEvent.php ├── Model │ └── Product.php └── composer.json ``` ] --- .pull-left[ ```bash src ├── AppBundle │ └── Service │ └── ProductService.php └── ApiBundle ├── Controller │ └── ProductController.php * ├── Client * │ └── ProductService.php ├── Event │ └── ProductEvent.php ├── Model │ └── Product.php └── composer.json ``` ] .pull-right[ Le client (Guzzle) permettant d'appeler son application maître de manière synchrone. ] --- .pull-left[ ```bash src ├── AppBundle │ └── Service │ └── ProductService.php └── ApiBundle * ├── Controller * │ └── ProductController.php ├── Client │ └── ProductService.php ├── Event │ └── ProductEvent.php ├── Model │ └── Product.php └── composer.json ``` ] .pull-right[ Les routes et contrôleurs qui permettent à l'application maître de pousser les modifications. ] --- .pull-left[ ```bash src ├── AppBundle │ └── Service │ └── ProductService.php └── ApiBundle ├── Controller │ └── ProductController.php ├── Client │ └── ProductService.php * ├── Event * │ └── ProductEvent.php ├── Model │ └── Product.php └── composer.json ``` ] .pull-right[ L'évènement qui sera propagé dans l'application Symfony lorsqu'un appel sera reçu sur l'API. ```php class ProductEvent { /** @var string */ private $httpMethod; /** @var \RefProduit\ApiBundle\Model\Product */ private $product; } ``` ] --- .pull-left[ ```bash src ├── AppBundle │ └── Service │ └── ProductService.php └── ApiBundle ├── Controller │ └── ProductController.php ├── Client │ └── ProductService.php ├── Event │ └── ProductEvent.php * ├── Model * │ └── Product.php └── composer.json ``` ] .pull-right[ Les *modèles de données canoniques*. ] --- .pull-left[ ```bash src ├── AppBundle │ └── Service │ └── ProductService.php └── ApiBundle ├── Controller │ └── ProductController.php ├── Client │ └── ProductService.php ├── Event │ └── ProductEvent.php ├── Model │ └── Product.php * └── composer.json ``` ] -- .pull-right[ .img-100[![fabpot](images/fabpot.png)] Un `composer.json`, permettant son utilisation par les autres applications. L'API Bundle profite directement du *semver* de son application parente. ] --- layout: false # L'*ApiBundle* est un ambassadeur Il **peut contenir** : * des fonctionnalités techniques supplémentaires * gestion de cache * *Service Location* ??? Citer exemple gestion de cache ATP, contraintes de SLA Service location à éviter, réintroduit un couplage à l'infrastructure -- Il **ne contient jamais** : * la logique métier de son application maître * la logique métier de son application utilisatrice * les informations de persistance (mappings Doctrine) --- layout:true # Utiliser un API Bundle --- #### `composer.json` ```json { "require": { "arf/ref-produit-api-bundle": "1.0.0", "arf/stock-api-bundle": "1.1.2" } } ``` #### `routing.yml` ```yml ref_produit: resource: "@RefProduitBundle/Resources/config/routing.yml" prefix: /api stock: resource: "@StockBundle/Resources/config/routing.yml" prefix: /api ``` --- #### `services.yml` ```yml product_listener: * class: Arf\StockBundle\EventListener\ProductListener tags: - { name: kernel.event_listener, event: arf_product } ``` Le code métier va ici ! ??? Façon de parler, un Listener est un point d'entrée, pas réutilisable --- ## Appel du client Guzzle ```php class MaClasseMetier { public __construct(\Arf\RefProduitApiBundle\Service\ProductService $productService) { $this->productService = $productService; } public function maMethodeMetier() { $this->productService->list(); $this->productService->findOne(1); $this->productService->create(new \Arf\RefProduitApiBundle\Model\Product()); } } ``` --- layout:false # Point reactive -- ### .fa.fa-check-square-o[] Elastic ??? ### Elastic * API Rest (Stateless), donc scalable * Multi-threadé nativement par le serveur web -- ### .fa.fa-check-square-o[] Resilient ??? ### Resilient * -- ### .fa.fa-check-square-o[] Responsive ??? ### Responsive * Appels unitaires à l'API Rest, qu'il est possible de monitorer * HTTP permet une gestion du cache qui stabilise les perfs -- ### .fa.fa-check-square-o[] Testable ??? ### Testable * NelmioApiBundle * Postman * Browser -- ### .fa.fa-check-square-o[] Réutilisable ??? ### Réutilisable * Entre applis Symfony * Pour d'autres technologies -- ### .fa.fa-times[] Message driven ??? ### Orienté message * ça ne suffit pas * comment notifier plusieurs applications sans multiplier les appels HTTP ? ??? Et c'est là qu'on en arrive au... --- class: inverse, middle, center # Enterprise Service Bus --- layout:true # Pourquoi un ESB ? --- --- Parceque on veut intégrer ensemble les microservices en réflétant fidèlement **les process métiers**. -- Parceque on ne veut plus coupler les applications entre elles, **ni à leur infrastructure** (Filesystem, SFTP, RabbitMQ...). -- Parceque parfois, **PHP n'est pas la meilleure solution**. -- ## (booooh) ??? ESB est un investissement structurant et important, pas adapté à toutes les organisations --- layout: false # Ce que l'ESB doit fournir -- .fa.fa-square-o[] une très haute disponibilité ??? Dans un environnement clusterisé, de préférence -- .fa.fa-square-o[] une garantie de *delivery* des messages ??? La promesse de 0 messages perdus -- .fa.fa-square-o[] un *Message Oriented Middleware* (RabbitMQ, ZeroMQ, ...) -- .fa.fa-square-o[] un support complet de REST et des codes d'erreurs ??? * `500` : réveiller l'astreinte * `404` : réessayer dans 5 minutes -- .fa.fa-square-o[] un *Service Locator* pour ne pas paramétrer en dur les adresses physiques des applications -- .fa.fa-square-o[] un *routing* avancé des messages ??? Commande devant être préparée par un entrepôt externe ? On envoi le message au bon fournisseur -- .fa.fa-square-o[] le support des formats d'échanges des *tiers* ??? Tout ce qui ne peut pas recevoir un CDC On ne code plus de FTP, SFTP, etc... dans PHP Fin du crontab hell -- ### Chez Auchan : Talend ESB --- # Sans l'ESB .pull-left.img-100[ ![api1](images/API1.png) ] -- .pull-right[ * Idéal pour le développement * Synchrone * Couplage fort, uniquement un pour un * Pas orienté message ] ??? Bien adapté aux docker links --- # Avec l'ESB .img-100[ ![api2](images/API2.png) ] --- # Finally -- .center.middle[ ## .fa.fa-check-square-o[] Message driven ![champ](images/champ.gif) ] --- class: inverse, middle, center # ESB ≠ Magie ??? Problème : Développer les intégrations dans un ESB pas trivial. Besoin pour les équipes back-office d'être autonome sur les échanges. Pour communiquer avec l'extérieur, pas de solution, il faut mapper les CDC Au sein du SI, les ApiBundle et les CDC permettent uniformité. Uniformité = Automatisation possible --- layout:true # Bulle d'échanges --- ### Déclaration des applications à l'ESB ```json [ { "id" : "appli1", "url": "http://appli1.auchan.net/api/exchanges" }, { "id" : "appli2", "url": "http://appli2.auchan.net/api/exchanges" }, ... ] ``` --- ### *Manifest* des objets produits et consommés `GET http://appli1.auchan.net/api/exchanges` ```json { "consume": { "product": "http://appli1.auchan.net/api/exchanges/product", }, "produce": { "stock": "http://appli1.auchan.net/api/exchanges/stock", } } ``` ??? Automatisation des routes dans Talend --- layout:false # Bilan -- .pull-left[ ## The Good * urbanisation respectée * découplage réussi * domaine métier mieux maîtrisé * test des API automatisables * du code métier, que du code métier ] ??? ### Découplage réussi * n'importe quel composant peut être remplacé par une solution externe * l'ESB se charge de faire la médiation ### domaine métier mieux maîtrisé * Permet la spécialisation des équipes ### du code métier, que du code métier Plus du code PHP de gestion des flux, plus de crontab à rallonge -- .pull-right[ ## The Ugly * complexité de l'architecture * beaucoup de packages dans Satis ] --- # Reste à faire -- ### Monorepos -- ### Exploiter la bulle d'échange -- ### Propagation de l'authentification ??? Mais ça a avancé -- ### Automatiser les déploiements -- ### Évangéliser ??? Les développeurs + Les exploitants -- ### Vraiment évangéliser ??? Beaucoup de choses cool faites par des archi ou des équipes réduites Besoin de laisser les développeurs se l'approprier --- class: inverse, middle, center # Questions ? --- class: inverse, middle, center # Merci ! -- ## Use Wallabag ![w](images/w.png)