write.vanoix.com

Reader

Read the latest posts from write.vanoix.com.

from alexandre

Petite astuce, car non documentée officiellement, concernant Sylius. Si comme moi vous créez/utilisez des plugins, vous avez dû vous apercevoir que les nouveaux éléments de menus apparaissaient toujours en dernier.

Ce comportement est normal car il s'agit de listeners qui sont appelés les uns à la suite des autres en suivant l'ordre de déclaration. L'astuce consiste donc à créer un listener qui sera toujours appelé en dernier et qui ne servira qu'à modifier l'ordre des items.

Dans un premier temps, on va créer le listener.

<?php

declare(strict_types=1);

namespace App\Menu\Event;

use Sylius\Bundle\UiBundle\Menu\Event\MenuBuilderEvent;

final class AdminMenuListener
{
    public function reorderMenu(MenuBuilderEvent $event): void
    {
        $menu = $event->getMenu();
        $menu->reorderChildren([
            'catalog',
            'foo_menu', // plugin foo
            'bar_menu', // plugin bar
            'sales',
            'customers',
            'marketing',
            'configuration',
        ]);
    }
}

Puis on déclare la configuration de ce listener en pensant à lui donner l'ordre de priorité le plus bas (tous les éléments du menu doivent être connus avant d'être réordonnés).

services:
    App\Menu\Event\AdminMenuListener:
        tags:
            - { name: kernel.event_listener, event: sylius.menu.admin.main, method: reorderMenu, priority: -256 }

And voilà !

 
Read more...

from alexandre

Attention :

  • Cet article fait partie d’une série consacrée à de grands principes d’architecture qui me sont propres et a pour objectif de faire comprendre aux tiers comment évoluer sur mes propres projets.
  • Cet article évolue suivant les retours et les différentes expériences.
  • Le terme Domain n'implique pas l'obligation d'une conception orientée DDD.

Le design pattern ADR est basé sur le triptyque ActionDomainResponder. Il est l’une des alternatives au design pattern MVC.

Il est possible de vulgariser le pattern de la façon suivante :

  • Action : l’équivalent d’un contrôleur, dédié à une opération HTTP (une route) de l’application ;
  • Domain : le point d'entrée logique vers le code métier ;
  • Responder : en charge du traitement et du rendu de la réponse.

Pourquoi ?

Le pattern MVC invite à créer des controllers composés d'une ou plusieurs actions. A l'usage, ces controllers peuvent devenir conséquents voir problématiques car trop complexes. L'ADR permet de limiter cette complexité globale. Il sera toujours possible d'avoir du code complexe, mais cette complexité sera limitée à une action dans un contexte précis.

Étant donné que l'ADR est composé d'un triptyque, le pattern encourage naturellement la POO et un SRP simple dans le sens ou les traitements métiers seront plus naturellement délégués à des “services”.

Enfin, l'ADR via son Responder assure une cohérence dans le rendu des réponses de l'application.

Comment ?

Si une Action est sensiblement la même chose qu’un controller à savoir être en charge du traitement de la requête entrante, le Responder peut quant à lui être conçu de manière différente.

Le moyen le plus simple consiste à mutualiser les Responder en les dédiant à un type de réponse. Dans une application HTTP classique, on pourra ainsi créer différents types de Responder :

  • HtmlResponder ;
  • RedirectResponder ;
  • DownloadResponder ;
  • JsonResponder ;
  • XmlResponder ;
  • ...

Plus complexe, mais conforme à la définition initiale du pattern, le ou la développeur·euse dédiera une action donnée à un Responder unique. On utilisera alors cette spécialisation pour les cas où le formatage de la réponse est complexe.

Afin de conserver la qualité de notre application, ce Responder pourra encapsuler un Responder commun (via la composition).

Enfin, l'utilisation la plus complexe consistera quant à elle à ajouter un Payload afin de spécialiser la réponse ainsi que son contenu.

Dans une application orientée DDD, l'Action et le Responder seront situés dans la couche User Interface. L'emplacement de la couche Domain dépend quant à elle des choix d'architecture de l'application. N'y voyez, une fois de plus, qu'un terme commun à d'autres approches de conception.

Exemples

<?php

namespace App\UI\Front\Action;

use App\UI\Front\Responder\HelloResponder;
use App\Application\Service;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

final class HelloAction
{
    private Service $service;

    private HelloResponder $responder;
    
    public function __construct(
        Service $service, 
        HelloResponder $responder
    ) {
        $this->service = $service;
        $this->responder = $responder;
    }
    
    public function __invoke(ServerRequestInterface $request): ResponseInterface
    {
        // 1. entrée
        $data = (string) $request->getBody();
        
        // 2. traitement (Domain)
        $service = ($this->service)($data);
        
        // 3. sortie (Responder)
        return ($this->responder)($service);
   }
}
<?php

namespace App\UI\Front\Responder;

use App\Application\Service;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\HtmlResponse;

final class HelloResponder
{   
    public function __invoke(Service $service): ResponseInterface
    {       
        $response = $service->getData();
        
        return new HtmlResponse($response);
   }
}

Symfony

L'utilisation de l'ADR n'est pas mise en avant dans Symfony. Il faudra donc veiller à sensibiliser l'équipe à ce sujet. A contrario, l'autowiring de Symfony étant performant, il est possible d'utiliser l'ADR sans aucune modification particulière du framework. Il n'y a donc aucune contrainte technique.

De par sa conception, on évitera d'injecter les différents services utilisés par une Action dans la méthode invoke et on privilégiera l'injection via le constructeur.

Si l'utilisation de Twig n'a pour le moment jamais été remise en question par les auteurs de Symfony, des changements dans les best practices ont eu lieu à plusieurs reprises sur les controllers. L'utilisation de l'ADR a pu permettre de limiter l'impact lors des migrations. Il est concrètement possible de les implémenter depuis la version 2.7 de Symfony.

Questions/Réponses

Aucune TwitterEmail

Conclusion

L'ADR est une alternative très intéressante au MVC et adaptée au flux Request/Response. Le coût technique de son utilisation n'est pas important, mais son impact est fort. C'est également un cadre plus strict avec un fort pouvoir d'adaptation.

Certain·e·s lui reprocheront sont coût cognitif avec comme argument principal l'impact sur la directory structure du projet (il existe un fichier par action), mais il est alors facile de répondre que ce coût est largement compensé lorsque l'on visualise le code.

Pour terminer, cette segmentation permet également de faciliter les tests unitaires.

 
Read more...

from alexandre

Attention :

  • Cet article fait partie d’une série consacrée à de grands principes d’architecture qui me sont propres et a pour objectif de faire comprendre aux tiers comment évoluer sur mes propres projets.

  • Cet article évolue suivant les retours et les différentes expériences.

  • User Interface fait partie des termes utilisés en DDD. N'y voyez cependant qu'un terme commun et une inspiration (forte mais une inspiration tout de même).

Le terme Interface Utilisateur (User Interface ou encore UI) désigne les différents moyens d’une application de communiquer avec un système extérieur : humain ou machine.

On trouve différents ensembles d’Interfaces Utilisateur :

  • HTML
  • API
  • Console
  • Message Queue
  • ...

Tous ces ensembles peuvent eux-mêmes être divisés. Une Interface Utilisateur HTML pouvant par exemple représenter la partie vitrine d’un site internet et une autre Interface Utilisateur l’administration. Autre exemple, une Interface Utilisateur API peut quant à elle exister au travers de différentes versions clairement identifiées.

Pourquoi ?

Dans un système complexe et destiné à évoluer, il est plus simple de segmenter ses différentes Interfaces Utilisateur et de les identifier clairement.

Cette segmentation est aussi bien visuelle : les éléments d’une Interface Utilisateur sont regroupés dans un même package, que technique : il est plus facile de leur donner des prérequis différents, de les faire évoluer dans le temps ou encore de les déprécier.

Une Interface Utilisateur est également décorrélée des autres couches de l'application (qui sont quant à elles chargées du traitement métier). Cela sous-entend que différentes Interfaces Utilisateur effectuant le même traitement encourageront naturellement le ou la développeur·se à utiliser des services identiques et les bonnes pratiques SOLID.

Les Interfaces Utilisateur peuvent également fonctionner de manière autonome avec des données de test ou temporaires et simuler un traitement sans impliquer d’autres services (via de la donnée brute par exemple).

Supprimer une Interface Utilisateur ne devrait pas avoir d'impact sur les autres Interfaces Utilisateur mais seulement sur les systèmes extérieurs en lien avec l'Interface Utilisateur (exemple : moteur de recherche, visiteur...).

Exemples

<?php

namespace App\UI\Front\Action;

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\HtmlResponse;

final class Hello
{
    private Service $service;
    
    public function __construct(Service $service)
    {
        $this->service = $service;
    }
    
    public function __invoke(ServerRequestInterface $request): ResponseInterface
    {
        // 1. entrée
        $data = (string) $request->getBody();
        
        // 2. traitement
        $response = $this->service->__invoke($data);
        
        // 3. sortie
        return new HtmlResponse($response);
   }
}
<?php

namespace App\UI\Console\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class HelloCommand extends Command
{
    protected static $defaultName = 'app:hello';

    private Service $service;
    
    public function __construct(Service $service)
    {
        $this->service = $service;

        parent::__construct();
    }
    
    protected function configure()
    {
        // arguments
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): ResponseInterface
    {
        // 1. entrée
        $data = $input->getArgument('foo');
        
        // 2. traitement
        $response = $this->service->__invoke($data);
        
        // 3. sortie
        $ouput->writeln($response);

        return Command::SUCCESS;
   }
}

Éléments annexes

Tous les éléments en lien avec l’extérieur font partie de l'Interface Utilisateur.

Cela implique par exemple les formulaires. On créera ses formulaires (et les éléments relatifs à son traitement) dans un namespace tel que App\UI\{...}\Form\{DTO,Type,Handler,...}.

A propos des formulaires, ces derniers font partie de l'Interface Utilisateur car ils sont intimement liées aux données entrantes, données qui seront ensuite traitées par les autres couches de l'applications. Cela introduit un potentiel lien avec un composant tiers (comme le composant Form de Symfony) mais ce lien, dans ce cas précis, n'est actuellement pas considéré comme problématique car la valeur ajoutée globale de l'Interface Utilisateur est plus importante.

Dans le cas de l'intégration dans un système tiers (plugin, extension, surcouche...), il est conseillé de considérer l'outil principal comme une Interface Utilisateur. Cette considération permettra alors de segmenter clairement la partie du code en relation avec l'outil et le code métier ajouté. Une fois encore, le changement d'outil et donc d'Interface Utilisateur ne doit pas impacter le code métier.

Symfony

Certaines règles sont en désaccord avec les bonnes pratiques officielles Symfony concernant les formulaires :

  • L’Interface Utilisateur encourage l’utilisation des Data Transfer Object et un traitement en deux temps (les traitements backend devraient être agnostiques des Interfaces Utilisateur) ;
  • Dans le cas d’un même formulaire évoluant avec les mêmes données sources dans deux Interfaces Utilisateur, on encouragera la duplication du Type ;
  • Sur la base du point précédent, compte tenu de la faible probabilité d’avoir deux Interfaces Utilisateur remplissant le même rôle avec les mêmes données sources, on déconseillera l’utilisation des Extensions sur autre chose que des types primitifs fournis par le framework et/ou des bundles. Si de telles extensions doivent être créées, elles ne doivent pas faire partie du package UI mais de l'Infrastructure.

Questions/Réponses

Aucune TwitterEmail

Conclusion

L’Interface Utilisateur est l’une des couches composant une application. Il est important de la traiter aussi bien que le reste de l’application, car c’est celle qui, dans le temps, pourra être constituée de code spaghetti. Charge à vous, sur cette base, de trouver votre meilleure façon de faire, qu’il s’agisse d’un modèle de type MVC, ADR ou maison.

 
Read more...

from Emeric

Objectifs : Approfondir la notion de Rapid Application Development. Thèmes : Rapid Application Development, Long Terme, Direction Technique, Métier, Organisation. Longueur : ~1400 mots.

Long Terme ?

Avant de parler de Rapid Application Development (qu'on abrégera RAD dans la suite de cet article), il faut revenir sur la notion de terme d'un projet, sa période.

En général, on va considérer trois périodes possibles : le court, moyen et long terme. Wikipedia a une définition intéressante de la notion de long terme en économie :

On appelle longue période, celle au cours de laquelle la capacité productive de l'Offreur peut se modifier.

Dans un vocabulaire plus centré autour d'un projet informatique, on pourrait traduire par une période au cours de laquelle le service est amené à évoluer.

À contrario, l'article cité définit le court terme de cette manière :

On appelle courte période, celle dont la durée permet de faire varier la production, les équipements étant constants. Dans cet intervalle, c'est le taux d'utilisation ou d'emploi des équipements qui s'accroît et délivre un volume de production supérieur.

Un projet informatique court terme serait donc un projet où le service n'est pas amené à changer, seulement son intensité d'utilisation.

Enfin, le moyen terme y est défini comme la période égale à la durée de vie des équipements significatifs de l'activité : en informatique, la durée de vie des principales fonctionnalités d'un projet (hors fonctionnalités de support du métier).

Pour résumer : le court terme, c'est quand le projet informatique n'est absolument pas modifié. À partir du moment où l'on modifie uniquement des fonctionnalités annexes au métier, on est dans un projet moyen terme. Enfin, dès qu'on se met à modifier des fonctionnalités métier cœur, on se situe sur du long terme.

Rapid Application Development (RAD)

On entend beaucoup dire dans l'univers PHP que faire du RAD, c'est savoir utiliser EasyAdmin pour son client. Faire du RAD, c'est commencer un projet avec API Platform. Faire du RAD, c'est aimer concevoir son métier sous forme de CRUD.

Le RAD est une méthode formalisée et publiée par James Martin en 1991, basée sur de nombreux travaux durant les années 80. Elle est d'ailleurs considérée comme la base du postulat Agile, pour ceux qui connaîtraient mieux cette notion. Les objectifs de cette méthode étant de diminuer le time-to-market, améliorer l'évolutivité du produit, et diminuer les risques, par rapport aux méthodes “en cascade” :

Développer des logiciels dans un cycle qualifié d'itératif, d'incrémental et d'adaptatif.

Faire du RAD, c'est utiliser une méthodologie de développement logiciel qui consiste à : – concevoir petit à petit son logiciel, avec de courtes phases répétées de conception, d'implémentation, de test et de validation (itératif), – ajouter petit à petit les fonctionnalités du logiciel, avec des livrables fonctionnels à chaque nouvel ajout (incrémental), – et permettre la modification simple des fonctionnalités existantes, pour suivre au mieux les changements tactiques et stratégiques (adaptatif).

Mettre en place du RAD

En croisant les deux premières sections de cette article, on peut déduire une information intéressante :

Le long terme, c'est à partir du moment où les fonctionnalités principales d'une application évoluent. Le RAD, c'est une méthode basée sur l'évolution des fonctionnalités d'une application. Un projet en RAD doit être géré comme un projet long terme.

Oui, un projet en RAD doit être géré comme un projet long terme. Et voici quelques éléments pour aller dans ce sens.

Concéder du métier pour la technique

Le métier doit être organisé pour pouvoir faire des concessions pour la technique. Il peut s'agir de minimiser les fonctionnalités souhaitées, ou bien de prioriser différemment des sections d'un métier.

Si on prend l'exemple du métier Paiement d'une application, la décision peut être de n'accepter que le paiement via Paypal (minimisation des fonctionnalités). On peut aussi accepter le paiement via carte bleue sur la première année, et reconduire à une échéance plus lointaine l'acceptation du paiement en plusieurs fois (priorisation différente).

Produire du code accessible

La technique doit être organisée pour délivrer des produits à forte accessibilité. On peut documenter le code et l'infrastructure. Segmenter le code selon le métier permet de le documenter implicitement : il est plus aisé pour un nouveau développeur de le maîtriser car il utilise des termes proches de ceux du métier. Enfin, l'utilisation d'outils du “plus bas niveau commun” (le langage de programmation pour les développeurs, le système d'exploitation pour l'administration système) est une des démarches les plus fortes d'accessibilité. À l'inverse, l'utilisation de surcouches implique un besoin de spécialisation plus fort pour les (futurs) mainteneurs.

Dans notre implémentation du métier Paiement, on préférera par exemple l'utilisation d'un client PHP officiel de Paypal, plutôt que l'utilisation d'un bundle Symfony JMS Paypal. Niveau segmentation métier, un couplage faible dans un dossier src/Domain/Payment indépendant (inspiré de DDD), basé sur des interfaces et de la composition rendra le code plus simple à reprendre et à documenter.

Produire du code robuste

Les implémentations métier produites doivent être résilientes. Les tests automatiques permettent de s'assurer du fonctionnement normal du métier. À côté de ça, les revues de code peuvent apporter une vérification en amont et plus théorique des failles d'un système. De manière générale, la programmation défensive et ses concepts sont une bonne source d'inspiration pour aider à produire du code robuste.

Pour notre métier de Paiement, on pourrait tester unitairement les règles les plus complexes avec PHPUnit : calcul des prix, application des coupons de réduction. Pour un point aussi critique, il faudrait bien sûr définir et implémenter les comportements en cas d'échec à la moindre étape d'un paiement (que ce soit en retour de l'API, lors de l'insertion dans notre BDD, tout peut échouer).

Protocoliser les dépendances

Les dépendances doivent être placées derrière des protocoles définis par le métier. C'est bien entendu vrai pour les dépendances externes, mais c'est aussi recommandé pour les dépendances internes : au sein d'une même application, un sous-métier devrait communiquer avec un autre sous-métier à travers un protocole défini.

En reprenant l'exemple de notre Paiement, on définira un protocole (en PHP, le protocole le plus simple étant une interface) de paiement autour de notre dépendance au SDK Paypal : une méthode, ou un ensemble de méthodes, avec des paramètres et des valeurs de retour spécifiques à notre besoin métier. L'implémentation de ce protocole étant le seul code ayant une dépendance sur le SDK.

Former une équipe d'experts généralistes

L'équipe technique doit être en mesure d'adresser avec efficacité toutes ces problématiques. Pour ça, il va falloir composer avec un niveau suffisamment élevé d'expertise. Outre pour l'accessibilité et la robustesse du code, une équipe d'experts généralistes est nécessaire pour s'adapter à un métier qui peut changer de direction à n'importe quel moment et toujours trouver les meilleurs compromis.

Trouver de bonnes réponses à toutes les questions posées auparavant dans cet article nécessite une expérience globale sur les différents systèmes de Paiement existants. Il faut par exemple connaître les limites de Stripe, d'un point de vue technique, métier et financier. Il faut avoir expérimenté Dalenys pour savoir à quel point on peut l'intégrer derrière des protocoles simples.

Mettre l'humain au centre du dispositif

Le projet doit se construire autour de chacun de ses équipiers (on en revient aux méthodes agiles). Notamment autour de l'apprentissage de chacun. L'expérience, l'apprentissage passé qui permet de faire des choix plus efficace pour le contexte donné. La formation, l'apprentissage futur qui permet une meilleure efficacité sur le temps, un renouvellement des équipes et l'émergence de nouvelles options.

Pour finir avec le module de Paiement, la solution sera choisie en fonction des habitudes croisées des mainteneurs de ce métier. De la même façon, l'intégration d'un junior à l'évolution de ce métier pourra se faire via une micro-formation sur une heure, explicitant les objectifs, raisons et spécificités de l'implémentation.

C'est tout ce qu'on veut

Faire du Rapid Application Development, c'est faire un choix de complexité pour atteindre un niveau très fort d'adaptabilité pour le projet. C'est tout ce que voudrait n'importe quel client, n'importe quel product owner. Mais pour réussir, ça demande une organisation particulière et beaucoup d'expertise. Et dans un mauvais cadre, ou mal géré, ça peut coûter très cher.

En tout cas, ce n'est ni quelque chose de simple, ni quelque chose d'efficace tout le temps. Ni une méthode qui consiste à faire les choses le plus vite possible sans se poser de question. Et c'est encore moins faire du CRUD.

Liens

 
En savoir plus...

from Emeric

Objectifs : Découvrir la bibliothèque Nom pour faire du parsing de données en Rust. Thèmes : Développement, Rust, Nom, Parsing, Combinatory Parsing.

Consommer un message binaire

Je me suis remis à développer en Rust ces derniers temps, au travers de deux projets de serveurs. L'un d'eux est un serveur pour un jeu navigateur à base de WebSockets. Est donc arrivée à un moment la question :

Qu'est-ce qu'on a de bien à notre disposition pour parser des messages binaires en Rust ?

L'objectif de ce serveur en Rust étant d'avoir des performances excellentes – d'où la volonté d'avoir un protocole binaire bien conçu –, il fallait trouver un système léger et sans trop de complexité. Extensible et modifiable aussi (les spécifications du protocole changent souvent). Testable unitairement, si possible.

Et j'ai découvert Nom.

Manger de la donnée, la transformer en ensembles cohérents

Un message binaire, c'est une suite finie d'octets. En utilisant le protocole WebSocket, avec le type de message binary, on sait qu'on peut directement recevoir un message binaire. Pour exemple, voici le message Identité conçu pour le serveur (le client se présente au serveur) :

 0         1 2             3->X+3
+---------+---------------+------+
| 0x0 (8) | length X (16) | name |
+---------+---------------+------+

Sur le premier octet, on a l'opcode 0x0. Les deux octets suivants correspondent à la longueur X en octets du nom renseigné. Et enfin, X octets pour le nom en lui même.

Dans l'idéal, on souhaite que notre parser ait le prototype suivant :

fn parse_identity(input: &[u8]) -> Result<String, Error>;

Une méthode parse_identity, qui prend en paramètre une suite finie d'octets &[u8], et qui renvoie : * le nom, en tant que String, si le parsing réussit, * ou une Error descriptive si l'opération échoue.

On peut implémenter cette méthode de façon naïve :

fn parse_identity(input: &[u8]) -> Result<String, Error> {
    /// Consomme le premier octet, et vérifie que la valeur est 0x0. Sinon, renvoie une erreur.
    if let Err(error) = parse_opcode(input[0]) {
        return Err(error);
    }
    /// Puis consomme les deux prochains octets, et stock la longueur. Sinon, renvoie une erreur.
    let length = match parse_length(input[1..3]) {
        Ok(length) => length,
        Err(error) => return Err(error),
    }
    /// Puis consomme "length" octets si disponibles, et renvoie la chaîne. Sinon, renvoie une erreur.
    if 3 + length < input.len() {
        parse_name(input[3..3 + length])
    } else {
        Err(Error::new())
    }
}

fn parse_opcode(input: u8) -> Result<(), Error>;
fn parse_length(input: &[u8]) -> Result<u16, Error>;
fn parse_name(input: &[u8]) -> Result<String, Error>;

En regardant ce code, on peut visualiser un modèle de construction qui se répète : notre fonction parse_identity combine d'autres fonctions parse_x, chacune consommant petit à petit des données, et renvoyant soit une donnée construite, soit une erreur.

Ce modèle est la base d'une technique de parsing qui s'appelle le Combinatory Parsing.

Implémenter le parser avec Nom

Maintenant qu'on a une idée du fonctionnement global, on va pouvoir s'intéresser à une implémentation concrète en utilisant la bibliothèque Nom, ici en version 5.1.2.

Nom va fournir un ensemble complet de fonctions utilitaires, combinables, pour définir notre propre parser. La plus grosse difficulté consiste à connaître les parsers disponibles et savoir quand les utiliser.

On va commencer par recréer le prototype de notre fonction parse_identity, en utilisant Nom :

use nom::IResult;

fn parse_identity(input: &[u8]) -> IResult<&[u8], String>; 

IResult est un type définissant le résultat d'un parser Nom. Il a 3 paramètres : l'input restant (ici &[u8]), l'output généré (ici String), et optionnellement l'erreur lancée (ici implicitement (&[u8], nom::error::ErrorKind)).

Puis, on va implémenter une première partie, la récupération de l'opcode :

use nom::{Err, IResult};
use nom::bytes::complete::tag;
use nom::error::ErrorKind;

fn parse_identity(input: &[u8]) -> IResult<&[u8], String> {
    /// On crée un parser qui reprend le parser "tag" fourni par Nom.
    let parse_opcode = tag([0]);

    /// On exécute le parser, et on récupère l'input restant.
    let (input, _) = parse_opcode(input)?;

    /// On renvoie l'input restant et une erreur.
    Err(Err::Error((input, ErrorKind::Tag)))    
}

Le principe du parser tag est d'essayer de consommer la suite d'octets donnée, ou de renvoyer une erreur.

Ensuite, on veut implémenter notre parse_length et notre parse_name :

use nom::{Err, IResult};
use nom::bytes::complete::{tag, take};
use nom::combinator::flat_map;
use nom::error::ErrorKind;
use nom::number::complete::be_u16;

fn parse_identity(input: &[u8]) -> IResult<&[u8], String> {
    let parse_opcode = tag([0]);
    let parse_name = flat_map(be_u16, take);

    let (input, _) = parse_opcode(input)?;
    let (input, name): (_, &[u8]) = parse_name(input)?;

    match String::from_utf8(name.to_vec()) {
        Ok(name) => Ok((input, name)),
        Err(_) => Err(Err::Error((input, ErrorKind::Tag)))
    }
}

On utilise ici une combinaison de be_u16 pour récupérer la longueur sur deux octets en Big Endian, de take pour récupérer une quantité précise d'octets, et de flat_map pour automatiquement passer le résultat du premier parser en paramètre du second. On essaye de convertir le contenu de name en chaîne de caractère UTF-8 (ça peut échouer si la séquence d'octets n'est pas une séquence UTF-8 valide), et la fonction est désormais correctement implémentée !

Cela dit, on va faire encore une petite révision. Actuellement, le parser utilise un style séquentiel. On appelle manuellement d'abord parse_opcode, puis si ça ne renvoie pas d'erreur, on jette le résultat et on appelle parse_name. Il y a une fonction Nom pour ça :

use nom::{Err, IResult};
use nom::bytes::complete::{tag, take};
use nom::combinator::flat_map;
use nom::error::ErrorKind;
use nom::number::complete::be_u16;
use nom::sequence::preceded;

fn parse_identity(input: &[u8]) -> IResult<&[u8], String> {
    let parse_opcode = tag([0]);
    let parse_name = flat_map(be_u16, take);
    let parse_identity = preceded(parse_opcode, parse_name);

    let (input, name) = parse_identity(input)?;

    match String::from_utf8(name.to_vec()) {
        Ok(name) => Ok((input, name)),
        Err(_) => Err(Err::Error((input, ErrorKind::Tag)))
    }
}

Le parser preceded prend en paramètre deux parsers, qu'il appliquera séquenciellement, et ne renverra que le résultat du second. On se retrouve maintenant avec une seule méga-combinaison pour notre parser (on a juste la conversion en chaîne de caractère UTF-8 qui est une étape séparée).

Installer Nom

Pour installer Nom dans un projet rust, il suffit d'ajouter la dépendance nom = "5.1.2" (version actuelle) au fichier cargo.toml.

Un dernier point : Nom est totalement compatible avec des tests unitaires Rust. On peut ajouter au fichier créé auparavant un module test derrière un flag de compilation #[cfg(test)] (qui excluera des binaires compilés ce module), contenant directement le test unitaire. Ce qui donne un fichier final avec :

use nom::{Err, IResult};
use nom::bytes::complete::{tag, take};
use nom::combinator::flat_map;
use nom::error::ErrorKind;
use nom::number::complete::be_u16;
use nom::sequence::preceded;

fn parse_identity(input: &[u8]) -> IResult<&[u8], String> {
    let parse_opcode = tag([0]);
    let parse_name = flat_map(be_u16, take);
    let parse_identity = preceded(parse_opcode, parse_name);

    let (input, name) = parse_identity(input)?;

    match String::from_utf8(name.to_vec()) {
        Ok(name) => Ok((input, name)),
        Err(_) => Err(Err::Error((input, ErrorKind::Tag)))
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_parse_identity() {
        let data = vec![0, 0, 2, 49, 49];
        assert_eq!(
            parse_identity(&data),
            Ok((
                &data[5..5],
                String::from("11")
            ))
        )
    }
}

Comme tous les tests en Rust, ils sont exécutables en utilisant la commande cargo test. C'est vivement recommandé d'associer des tests à chacun des parsers conçus : c'est rapide à relancer et pratique pour du TDD.

Liens

 
En savoir plus...

from alexandre

TL;DR : une note (très personnelle) concernant mon parcours de conférencier avec un petit teaser concernant l'avenir.

Au commencement

J'ai commencé à donner des conférences avec l'AFUP (pour l’antenne de Lyon) en 2014. Je faisais 20kg de moins, j'étais rasé de près et j'évoquais alors l’utilisation de la sémantique dans le but de faciliter la conception du modèle d’un projet. J'ai ensuite rapidement enchaîné sur le sujet de l’intérêt d’ouvrir ses process au travers d’une conférence sur l’entreprise Open Source.

Ma première grosse conférence au Forum PHP parlait quant à elle de l’organisation de projets DDD. J’ai organisé, à peu près au même moment, le DDD-day à Lyon. Il y a eu ensuite différents REX [1] [2] sur des antennes locales et au travers de différents Meetups/BBL pour terminer sur la récente conférence de l’AFUP Day consacré à la programmation défensive.

Entre temps j’ai parlé d’autres sujets, mais toujours très peu de technique. Pour rester factuel, environ 90 % de mes conférences techniques ont eu lieu sur Bitcoin et sur la création de Smart Contract Ethereum. Le tout avec différents niveaux de technicité suivant le public présent. Je n’ai jamais réussi à parler de tout ça dans l’écosystème PHP (sauf chez M6Web), c'est officiellement mon seul regret jusqu'à maintenant.

L'avenir

Je ne proposerai plus de conférences sur mes thématiques de prédilections. Je pense avoir dit ce que j’avais à dire, plus ou moins bien, et tout ça sera encore valable pendant quelques années.

Si tout se passe bien, l'avenir sera consacré à des applications réelles, des choses concrètes, des choses que l’ont peut voir et toucher du doigt (vous l’avez ?) et d’autres idéaux.

Je sais que c'est encore un peu (trop) vague. Certains sujets mettent du temps à être posés et nécessitent énormément de travail en amont. J'ai commencé en début d'année, confinement, nécessité de se mettre à fond pour rattraper le temps. Ça arrivera forcément à un moment.

En attendant

J’avais annoncé en début d’année que je me tenais à disposition des antennes AFUP locales et de tous les autres. C'est toujours le cas et j’aurais bien entendu immensément de plaisir à retravailler ces sujets que je ne quitte pas professionnellement (bien au contraire) puis à vous les présenter. N'hésitez pas à venir en discuter.

Pour conclure, j'espère sincèrement que l'on pourra tous se voir au Forum PHP et que coco le virus ne viendra pas nous embêter. A bientôt ❤️.

 
Read more...

from alexandre

J'ai laissé un peu d'eau couler sous les ponts et ai finalement décidé de partir sur deux possibilités :

  • Utiliser “main” pour la majorité de mes projets. Un consensus a émergé très rapidement autour du terme qui est suffisamment explicite à mon gout ;
  • Utiliser l'option proposée par Fabien Potencier pour les projets plus conséquents.

Deux précisions tout de même.

Je pense avoir donné mon point de vue au travers de différentes discussions. Cela fait très longtemps que je pense que la société ne va pas dans le bon sens. J'en discute régulièrement et énormément avec certaines personnes. Je suis comme beaucoup très embêté que cela aille jusqu'à la remise en question de termes techniques, MAIS le fait est que cela me semble nécessaire à bien des égards et que toutes les discussions autour de ce sujet ont été utiles pour certain.e.s. Si changer le nom de mes branches me permet d'avoir encore de nouvelles discussions autour du sujet et que ces discussions permettent à un peu plus de personnes de prendre conscience des choses, ça me convient.

À l'opposé, penser que l'on règle le problème en renommant sa branche principale sur GIT est aussi stupide que d'illustrer ce changement avec des extrêmes qui n'ont rien à voir. Le monde du web est trop peu politisé et je pense que cela n'est pas une bonne chose. Nous avons la capacité de faire beaucoup plus grâce à nos lignes de code et le temps est venu d'endosser ce rôle.

J'espère sincèrement que vous prendrez le temps d’élever le débat dans votre for intérieur et que vous vous intéresserez aux “vrais” problèmes, ceux qui sont liés à ces discussions. Ils existent, ils ont besoin d'être adressés et nous sommes en capacité d'essayer de les résoudre.

 
Read more...

from Emeric

Objectifs : Réfléchir à la gestion des dépendances dans un projet, et résoudre le problème de la dépendance incontrôlée sur Composer. Thèmes : Développement, PHP, Dépendances, Composer, Déploiement.

Dependency manager

L'utilisation de dépendances dans une stack logicielle est soumise à une règle d'or :

Pour chaque dépendance, mettre en place un contrôle des versions autorisées, en fonction de la confiance accordée à son mainteneur.

Dans l'univers JavaScript, on utiliserait Yarn, qui permet via un package.json de décrire un schéma des versions de dépendances acceptées.

Côté PHP, on a pris l'habitude d'utiliser Composer depuis quelques années pour gérer nos dépendances. Et ça donne :

{
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

Exemple tiré de la documentation officielle de Composer

Dans cet exemple, on intègre le package monolog/monolog à notre projet, en explicitant la contrainte : version 1.0, ou tout patch sur cette version mineure.

Semantic versioning

Tout ça peut fonctionner car basé sur Semantic Versioning, une RFC qui définit un ensemble de règles pour la numérotation des versions (qui crée donc une cohésion sur la signification des numéros de version).

Dans l'exemple précédent, c'est Semantic Versioning qui nous permet de traduire la numérotation 1.0.* en version 1.0, ou tout patch sur cette version mineure.

Ensuite, c'est la confiance qu'on a envers les mainteneurs de la dépendance monolog/monolog qui nous permet d'être souple et d'accepter tous les patchs qui pourraient être publiés.

Dans le cas où l'on intègre la dépendance d'un mainteneur auquel on accorde moins de confiance, on peut remplacer par "monolog/monolog": "1.0.8", afin de laisser la place au moins de risque possible (la version choisie ayant été testée directement lors des développements, et étant donc fonctionnelle à un moment T).

Verrouillage

Le dernier mécanisme — nécessaire — apporté par les Dependency Manager modernes est le mécanisme de verrouillage des dépendances. Il permet de verrouiller les versions des dépendances de manière stricte lors de phases de release (jusque là, l'application des contraintes de version se fait lors des phases de développement).

Dans le cas de Composer, ce mécanisme est implémenté via le fichier composer.lock :

{
    "_readme": [
        "This file locks the dependencies of your project to a known state",
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
        "This file is @generated automatically"
    ],
    "content-hash": "2bdb78875a3c225905ed078c9d9140f6",
    "packages": [
        {
            "name": "psr/cache",
            "version": "1.0.1",
            "source": {
                "type": "git",
                "url": "https://github.com/php-fig/cache.git",
                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
                "shasum": ""
            },
            ...
        },
        ...
     ]
}

On voit que ce fichier (qui doit faire partie des sources à release) fixe la dépendance psr/cache à la référence de commit précise d11b50. On maîtrise donc entièrement quelle version de notre dépendance va être utilisée lors d'un déploiement.

Recommandation officielle

#!/bin/sh

EXPECTED_CHECKSUM="$(wget -q -O - https://composer.github.io/installer.sig)"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"

if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
then
    >&2 echo 'ERROR: Invalid installer checksum'
    rm composer-setup.php
    exit 1
fi

php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT

Script d'installation automatique proposé par la documentation officielle de Composer

Composer propose un utilitaire d'installation, signé, sous la forme du script PHP composer-setup.php. La documentation indique qu'on peut choisir la version de l'utilitaire à utiliser. Cependant, il n'est pas indiqué qu'on DEVRAIT choisir la version de Composer à installer, et comment faire.

Cordonnier mal chaussé

Fin 2018, Composer sortait sa version 1.7.3 (un patch sur une version mineur, donc). Le script d'installation ci-dessus étant massivement utilisé, fleurirent sur Twitter et Github une pluie de commentaires de type :

Yesterday composer install works fine. Without any changes to composer.json & composer.lock today it fails

https://github.com/composer/composer/issues/7764 https://github.com/composer/composer/issues/7974 https://github.com/composer/composer/issues/7813

Et des réponses des mainteneurs du type :

This isn't a bug, it was a bug before which was fixed.

Avec l'arrivée de Composer 2 dans les prochains mois, les scripts de déploiement en production des applications PHP risquent de se mettre à planter. On commence à voir apparaître dans les logs de déploiement des Deprecation Notices qui n'existaient pas quelques mois auparavant.

Être strict AUSSI avec Composer

Les mainteneurs de Composer ont fait un travail admirable sur la stabilité du projet, et ont permis que puisse être utilisée quasiment n'importe quelle version de l'outil sans causer de problème. La confiance s'est donc installée, et la sécurité énormément relâchée. Afin de passer correctement le cap des prochains mois, il sera sans doute judicieux d'être à nouveau strict avec le Composer souhaité sur chaque projet.

Le script d'installation automatique pour Linux propose (voir les sources sur Github, bien que cela ne semble pas documenté) plusieurs options, dont --version qui nous intéresse particulièrement :

Composer Installer
------------------
Options
--help               this help
--check              for checking environment only
--force              forces the installation
--ansi               force ANSI color output
--no-ansi            disable ANSI color output
--quiet              do not output unimportant messages
--install-dir="..."  accepts a target installation directory
--preview            install the latest version from the preview (alpha/beta/rc) channel instead of stable
--snapshot           install the latest version from the snapshot (dev builds) channel instead of stable
--version="..."      accepts a specific version to install instead of the latest
--filename="..."     accepts a target filename (default: composer.phar)
--disable-tls        disable SSL/TLS security for file downloads
--cafile="..."       accepts a path to a Certificate Authority (CA) certificate file for SSL/TLS verification

L'option --version semble accepter en paramètre n'importe quel tag disponible au niveau du dépôt git Composer. Pour installer une version précise de Composer, on peut donc remplacer dans notre script :

php composer-setup.php --quiet --version="1.10.6"

Il ne semble malheureusement pas y avoir de possibilité d'utiliser une contrainte plus souple de version (de type 1.10.* ou ^1.10) à ce jour.

Liens

 
En savoir plus...

from Emeric

Objectif : Découvrir quelques astuces pour mieux gérer l'environnement de développement de son équipe. Thèmes : Développement, Docker-Compose, Makefile, PHP.

Docker-Compose

On débute avec seulement notre projet PHP. La première chose à faire, c'est de mettre en place l'outil qui nous permettra de créer, de recréer, à volonté, la stack qui est utilisée pour développer sur le logiciel.

On va vouloir définir l'état actif normal de notre stack de développement. Et uniquement cet état. Quelles parties du logiciel doivent tourner obligatoirement ? Lesquelles sont à contrario temporaires ? Cet état actif normal, on va vouloir l'écrire dans un fichier, et laisser notre outil gérer lui même les actions à effectuer pour l'atteindre.

Pour ça, il y a Docker-Compose. C'est un ensemble de commandes qui vont encapsuler l'accès à Docker – qu'on utilise, lui, pour gérer toutes les problématiques de portabilité –. On écrit dans un fichier descriptif, conventionnellement docker-compose.yml, l'état de la stack qu'on souhaitera faire tourner via Docker.

Un exemple de docker-compose.yml, utilisé pour faire tourner l'infrastructure de développement en local de green.vanoix.com :

version: '3.7'

services:
    application:
        build: ./docker/build/application
        volumes:
            - .:/var/www/app
        ports:
            - "80:80"
        networks:
            - backend
        secrets:
            -
                source: ssh_key
                target: /root/.ssh/id_rsa
    database:
        image: mariadb:latest
        volumes:
            - ./docker/data/database:/var/lib/mysql
        environment:
            - 'MYSQL_ALLOW_EMPTY_PASSWORD=yes'
            - 'MYSQL_DATABASE=application'
        networks:
            - backend

secrets:
    ssh_key:
        file: ~/.ssh/id_rsa

networks:
    backend: ~

On décrit : – D'abord les services qu'on va vouloir faire tourner, les volumes qui leur seront rattachés, etc. – Puis les secrets qu'on va vouloir utiliser. Qu'on va ensuite ajouter à chaque service où c'est nécessaire. – Enfin les réseaux qui devront être créés. Qu'on renseigne, de la même manière, au niveau des services qui les utiliseront.

Makefile

Maintenant que notre cible est définie, on va vouloir la rendre accessible, rapidement. D'une part, on cherche à diminuer la difficulté à mettre en route cette stack. D'une autre, on cherche à diminuer le temps pour ce faire.

Il va donc falloir réduire au maximum le nombre d'étapes, et automatiser chacune le plus possible.

Pour sa portabilité, le fait qu'il soit un standard, et qu'il permet de gérer tout ce qu'un shell peut gérer – donc, tout –, on va utiliser make. De manière assez conventionnelle, la règle principale par défaut est souvent nommée install. On retrouve aussi la règle run (ou start), qui permet de lancer le logiciel. Souvent la règle clean pour nettoyer l'environnement local. Et parfois, stop dans le cas d'un service en arrière-plan.

Dans notre cas, un fichier Makefile contenant ceci devrait suffir. Moins il y a de règles, plus le projet est simple à aborder :

.PHONY: start stop

start:
    docker-compose build
    docker-compose run application composer install
    docker-compose up -d

stop:
    docker-compose down

Rappel sur le .PHONY : chaque règle (ou cible) est normalement associée au fichier du même nom. Pour décorréler les deux, on renseigne la règle dans l'énumération .PHONY.

Makefile et Docker-Compose

Le principal problème avec ce que l'on a fait, c'est qu'on va essayer de rebuild les containers et réinstaller les dépendances Composer, à chaque lancement du projet. On perd beaucoup de temps. D'un autre côté, si on souhaite seulement réinstaller les containers, on démarre aussi l'application. On perd beaucoup en ressources. Cela traduit un problème de conception, sur les différents cas d'utilisation de nos automatismes.

Pour corriger cela, on va définir les états actifs normaux de l'environnement de développement. Puis les décrire, dans un fichier, sous forme de règles idempotentes. Puis – comme avec docker-compose – on considérera que le travail de notre outil n'est que de mettre le système dans l'état souhaité.

Ici, on définit trois états pour un environnement local : – l'état installé, lorsque les containers sont construits et les dépendances logicielles installées, – l'état démarré, lorsque le logiciel a été lancé, – et l'état stoppé, lorsque le logiciel a été arrêté.

make et docker se combinent à merveille, afin de définir les règles de passage à un état spécifique. Voici l'exemple du Makefile utilisé pour le projet green.vanoix.com :

.PHONY: install start stop

install:
	docker-compose build
	docker-compose run --no-deps --rm application composer install

start:
	docker-compose up -d

stop:
	docker-compose down

On définit une règle par état ciblé, et une série d'action pour chaque règle. Sur un plan plus technique, l'astuce consiste à profiter de la capacité de Docker-Compose à éxécuter seul (run --no-deps) un container temporaire (--rm), basé sur un service à l'arrêt (application), pour installer les dépendances PHP sur le système de fichiers (composer install).

README

Dans le cas d'applications plus complexes, il reste encore une chose à paufiner : la gestion du contexte. Souvent, on va considérer que le développeur devra avoir tel prérequis installé sur sa machine. Tel service lancé en fond. Telle configuration SSH. Tel dossier à tel emplacement. Telle connaissance acquise.

On va vouloir gérer tous ces cas particuliers. Mais sans sacrifier les performances et la simplicité de notre système. On peut encadrer notre environnement de développement en utilisant des outils de communication.

Le fichier README.md est classique et efficace, et un excellent liant avec tous les autres outils. Un exemple de template minimal qui permet de gérer la configuration du host et clef SSH sur un projet :

## Dev Environment

This project uses Docker (docker-compose version >=3.5) to manage the local development environment.

### Requirements

#### Hosts Configuration

You must have this entry in your `/etc/hosts` file:

```
127.0.0.1 myproject.localhost
```

#### SSH key forwarding

The project uses Docker Secrets to handle the SSH key forwarding to your container (mainly used to access private repositories).

Your local SSH key must be located at `~/.ssh/id_rsa`.

### Installation

```
make install && make start
```

The project should be now successfully running.

Liens

 
En savoir plus...

from Jérémy James

La naissance du projet

Octobre 2019, Vanoix est sponsor du Forum PHP 2019 organisé par l'AFUP. Comme tout bon sponsor, nous avons un budget alloué à l'événement et nous devons trouver une idée de goodies pour les 700 participants prévus.

À ce moment là, je viens tout juste d'être recruté chez Vanoix et j'ai envie de faire les choses un peu différemment de ce qu'il se fait d'habitude pour ce genre d'événement. Les stylos/carnets/stickers, c'est sympa, mais notre belle planète en a un peu marre de tout ce plastique. Nous décidons donc de partir sur quelque chose d'utile, sans plastique et dont l'impact écologique sera le plus faible possible.

Après de nombreuses discussions en interne avec Alexandre et Emeric, la décision est prise. Chaque participant aura son petit pot en fibre de bambou recyclable, une plaquette de tourbe et un sachet de graines. Chouette idée, n'est-ce pas ?

La logistique

Chouette idée, je vous le confirme. En revanche, niveau logistique, ce n'a pas été de tout repos.

Le postulat de base étant de réduire au maximum les coûts et l'impact écologique, nous avons voulu tout faire à la main. Je vous laisse imaginer la complexité de produire 700 pots, avec chacun de la terre et des graines. Nous nous sommes donc tournés vers un fournisseur d'objets publicitaires écologiques français, qui nous a fourni les 700 kits de plantation.

Une fois les kits reçus et après un samedi après-midi éprouvant à coller des stickers et des QR code sur les pots, un par un, nous sommes fin prêts pour le Forum PHP.

Un dernier obstacle se glisse sur notre chemin : mais comment donc allons-nous transporter tout ça ? Après moult discussions, toute l'équipe de Vanoix montera à Paris à bord d'une voiture de location, le coffre rempli à ras bord de pots de fleurs.

Un QR code ?

Pour les curieux qui ont scannés le QR code, vous tombez directement sur l'application green.vanoix.com.

Le principe étant de mettre en place un suivi très simple des plantes. Vous avez la possibilité de déclarer votre plante, de renseigner ou pas votre compte Twitter et le compte Twitter de votre entreprise si vous le souhaitez. Il est tout à fait possible de rester anonyme, mais de déclarer quand même sa plante. Il est aussi possible et même fortement recommandé, de mettre en ligne des photos et de partager le profil publique de votre plante sur les réseaux sociaux ! Spread the love! 💚

Petite information supplémentaire, si vous avez renseigné au moins un compte Twitter, nous partagerons vos plus belles photos avec le compte Twitter Vanoix en vous mentionnant.

Enfin, aucun suivi de vos données n'est en place sur le site. Nous avons fait le maximum pour être respectueux du RGPD et le site se veut le moins gourmand en ressources possible !

Le jour J

Les participants ont été très agréablement surpris de voir cette petite plante dans leur sac de goodies. Nous avons eu énormément de retours positifs, que ce soit sur Twitter, à notre antenne au Canada ou sur place au Conference Center du Marriott Rive Gauche à Paris. J'en profite pour vous dire merci à tous pour l'amour et la bienveillance dont vous avez fait part ! 💚

À l'heure où j'écris ces lignes, peu de plantes ont été déclarées. Une vingtaine sur 700 pots distribués. Je suppose que : – Les plus à l'aise avec les plantes attendent le bon moment pour planter leur tournesol ou leur capucine. – Nous avons trop peu communiqué sur ce projet, ou pas de la bonne manière. – Le format QR code ne convient pas (illisible ? technologie obsolète ?)

Dans tous les cas, nous sommes fiers d'avoir pu vous proposer cette aventure. Même si certains ne joueront pas le jeu de l'application, la plante sera quand même sur leur bureau. Et ça, ça nous fait chaud au cœur !

Fun facts

L'imprimante L'imprimante des QR code a sorti une page blanche au milieu des 24 pages non numérotées. Quelle joie immense de devoir comparer à l'oeil nu des planches de QR codes en 25x25mm. Merci à Emeric pour ses yeux laser et son sens du pixel aigu !

Au dernier moment L'application a été finalisée la veille du Forum à 1h du matin 🤯

Un déploiement en production a été fait en urgence le matin même, à 9h00. Pourquoi cela ? Parce que je me suis rendu compte en scannant le QR code du pot situé dans mon sac de goodies, que le serveur bloquait les uploads de plus de 2 méga octets. Impossible donc de mettre en ligne une photo prise avec un téléphone récent. Le coupable ? Le proxy Nginx mis en place en urgence la veille pour tenir la charge.

Tenir la charge ? Naïvement, nous avons cru que tous les participants allaient scanner leur QR code en même temps, par curiosité et qu'un pic de trafic allait faire tomber le serveur. Nous avons donc décidé de passer les 5h de trajet aller dans la voiture, la veille, pour mettre en place des optimisations, un système de cache, etc.

Le pic de trafic n'a jamais eu lieu 😭

Voici un petit aperçu des ressources consommées par le serveur sur les 7 jours qui ont suivi le forum PHP. On est large !

Bonnes pratiques Nous voulions avoir une application robuste, disponible, qui consomme peu de ressources et reste utilisable en mode partiellement dégradé. Nous avons donc suivi, au maximum, les bonnes pratiques de Defensive programming.

D'un autre côté, cela m'a permis de me remettre dans le bain de PHP/Symfony après 2 ans de Python. Merci Emeric pour tous les conseils et les reviews de code.

Le mot de la fin

Si le projet vous a plu, n'hésitez pas à nous le faire savoir. Il est possible que ce système de plante à scanner soit de nouveau présent dans les futurs événements que Vanoix sponsorisera.

N'attendez donc pas pour scanner votre plante 🌱 ! Encore merci à tous !

 
En savoir plus...