Ecrire un mail, c’est pas facile

Vous ne vous en rendez pas compte, parce que vous utilisez la plupart du temps un éditeur WYSIWYG, mais écrire un mail, c’est loin d’être simple.

Plus exactement, c’est à peu près facile si vous écrivez un mail en ASCII.

Pourquoi je vous parle de ça ?

Parce que je suis dans ma réécriture de rss2imap en Rust (qui s’appelle – follement – rrss2imap), et que j’ai buté pendant bien longtemps sur un problème délicat … Et que je m’en vais vous détailler.

En gros, mon implémentation initiale reprenait l’idée de rss2imap : j’avais ce template tera :

Dans ce template, je remplace les différentes entêtes par leurs valeurs … Et c’est là que ça coince … Parce que si je veux mettre autre chose que de l’ascii, je dois l’encoder, soit en quoted-printable, soit en base64. Pour être honnête, j’avais commencé à l’implémenter. Et j’ai commencé à rencontrer des problèmes marrants (par exemple, un retour à la ligne dans le sujet du mail foutait un bazar pas possible).

Donc j’ai abandonné l’idée cette semaine, et méthodiquement testé les différentes crates de création de mails

  • lettre-email qui était pas mal, mais utilisait time plutôt que chrono pour gérer les dates. Du coup j’ai dû abandonner parce que la conversion était infernale.
  • email-format qui ne fournit aucun support hors ascii.
  • emailmessage qui finalement était la meilleure solution : typesafe, facile à utiliser, avec un support hyper complet des différents encodages.

Et avec ça, j’ai enfin réussi à gérer correctement mes messages émis. Par contre, j’ai toujours des problèmes un peu pénibles de release qui ne marche pas pour Linux et Raspberry (ce qui est pénible puisque l’objectif est quand même clairement de faire tourner le script sur mon Raspberry).

Publicités

[DevFest] Designers, Développeurs, créons la différences!

Aujourd’hui, tous les sites se ressemblent, parce que tous les projets utilisent une bibliothèque de composants sans y réfléchir.

On va voir comment faire autrement en prenant l’exemple de l’aquarium de Florent.

Florent a un aquarium, donc une application pour le gérer.Son application est considérée évidement comme moche par le client (et c’est normal).

Donc typiquement, le client appelle un designer après coup.

Après quelques échanges entre le designer et le client, ce designer produira un nouveau layout qu’il ne saura pas communiquer au développeur. Autrement dit ça ne marchera pas.

Une autre approche, c’est de faire intervenir le designer dès le départ. Comme ça, le designer peut faire des recherches sur les utilisateurs et leurs besoins en prenant son temps. Pendant ce temps, les développeurs peuvent commencer à travailler et à produire de la valeur métier en tenant compte de ce besoin UX. Une fois les parcours utilisateurs identifiés, le designer peut commencer à produire des wireframes. C’est le moment de partager les informations entre designers et développeurs. On peut utiliser par exemple Figma, Zepelin, …​ En parallèle, le designer travaille sur le mood board dont il va tirer des éléments suscitant des émotions. Avec ces outils, le designer travaille sur des maquettes réelles pour mobile et desktop. Ces maquettes utilisent le concept d' »atomic design » où on construit les éléments du plus petit au plus grand. Et pour démontrer ces éléments, l’équipe utilise storybook. Dans ce design atomique, les premiers éléments sont

  • les icônes (en SVG plutôt qu’avec des font-icons)
  • les animations
  • la grille

Avec ça, on peut créer des composants plus avancés (comme des boutons) qu’on peut concevoir en peer programming …​ et démontrer également dans storybook.

Tous ces éléments peuvent aussi être partagés avec zeroheight.

Evidement, le design system doit être évolutif, comme le code. Il faut nécessairement conserver dans l’équipe un designer et un développeur.

Si on utilise malgré tout un framework de composants, il faut le considérer comme une dette technique assumée et le documenter comme le design system de l’application. Il ne faut pas oublier qu’un design system, avant tout, c’est du design, c’est donc indépendant de la technologie.

Conclusion

Florent et Cécile, des collègues, ont réussi à bien présenter les enjeux du développement web en 2019 : il ne faut pas seulement faire une application qui fonctionne, mais aussi présenter une belle expérience à l’utilisateur. Et la méthode et les outils présentés ici semblent très efficaces pour assurer une collaboration efficace.

[DevFest] Quarkus

Pour ceux qui ne liraient pas mes articles précédents, c’est un framework open-source (et Java) pour écrire des applications cloud-native, microservices ou serverless.

Et paf, on passe à la démo. Emmanuel a déjà créé une application Quarkus avec un front. Il lance donc le plugin maven (ça existe aussi pour gradle) avec mvn compile quarkus:dev. Ce plugin recharge l’application quand les ressources changent, en compilant le code au passage. On se retrouve avec une expérience de développement proche des langages interprétés.

Pour ajouter des dépendances, on ne prend pas directement des dépendances maven, mais des extensions (bon, en fait, si, mais c’est joliment rhabillé), qu’on peut ajouter encore une fois grâce au plugin. Évidement, si on ajoute une extension inexistante, le mesage d’erreur sera assez clair.

Revenons au code ! Emmanuel va coder une application de todo-list. Donc il crée un objet Todo qu’il va persister avec l’extension panache en mode ActiveRecord (comme dans Ruby on Rails par exemple).

Là, Emmanuel raconte un truc louche sur le fait que potentiellement, la classe Todo serait réécrite à la volée pour remplacer les champs publics par des champs avec getter/setter.

Dans les trucs malins, quand on active l’extension openapi, on a accès à l’interface swagger-ui uniquement en mode dev. Et c’est très bien comme ça !

Emmanuel package ensuite son application en natif.

 

Et c’est le moment d’expliquer pourquoi RedHat s’est lancé dans Quarkus.

Et vous pouvez relire à ce moment-là l’article écrit pendant DevoxxFr.

 

Guillaume Smet enchaîne avec un zoom sur les composants de GraalVM et ses prérequis. Avec GraalVM, on essaye d’éliminer au maximum le code mort pour accélérer l’application. Ca implique qu’on ne peut pas rajouter de classes, et donc ne pas faire d’introspection (sauf dans une liste de classes spécifiquement autorisées) ni de créer des proxies dynamiques.

Ca a un impact important, parce que la plupart des frameworks font précisément ce genre de choses au démarrage de l’application : charger des classes, scanner le CLASSPATH, détecter des classes spécifiques. Dans quarkus, on essaye donc de faire tout ça à la compilation, ce qui a un impact très positif sur l’exécution. Pour plusieurs raisons. D’abord parce qu’on fait moins de choses à l’exécution. Ensuite parce que si on charge la configuration à la compilation, on n’a peut-être pas besoin de parser de configuration à l’exécution (typiquement les parsers XML).

Conclusion

Comme beaucoup de monde, je suis à peu près sûr que Quarkus change la donne. Parce que Java était en perte de vitesse à cause des conteneurs. Et que Quarkus corrige ça … en ajoutant en plus un certain nombres de concepts très intéressants. Autrement dit, pour l’instant, Quarkus est la manière la plus moderne, la plus efficace (aussi bien en expérience développeur qu’en performance obtenue – mesurée en nombre de requêtes/seconde/Mo de Ram) de travailler dans l’écosystème Java. Et à l’heure où JavaEE meurt de mille blessures, c’est bien de retrouver un environnement efficace.

[DevFest] Micronaut

Vu de haut

Le framework a été créé il ya un an chez Object Computing par Graeme Rocher (qui a créé Grails avant).

Micronaut s’appuie sur les fondations de Spring (Boot) et Grails pour en tirer le bon :

  • l’injection de dépendances
  • l’auto-configuration
  • la découverte de services (Eureka, Zookeeper, Marathon, K8s, …​)
  • l’écriture de clients/serveurs HTTP

En évitant le moins bon

  • La réflexion
  • L’abus de proxies
  • La consomation mémoire
  • Le temps de démarrage
  • La testabilité

Comment ça marche ?

On peut coder en Kotlin/Java/Groovy. Dans les trois cas, autant que possible, on compile en ahead-of-time (par annotation processor en Java/Kotlin et transformation AST en Groovy). Du coup on peut lancer l’application dans la JVM …​ ou dans GraalVM.

Pour coder, on utilise la ligne de commande mn create-app qui va générer un build Gradle correctement configuré.

L’application « ressemble » à du spring boot classique. On peut évidement utiliser une API réactive (avec RxJava).

D’une façon intéressante, le test est écrit en Groovy avec Spock.

La configuration se fait de façon semblable à Spring Boot. On peut valider les paramètres des requêtes HTTP avec Bean Validation (et ça c’est cool). Evidement, comme c’est un framework web moderne, il est possible de récupérer de la télémétrie.

Et de déployer l’application chez les providers cloud connus (AWS, GAE, …​)

Qu’en tirer

Micronaut est jeune mais grandit rapidement. Du coup certains composants peuvent manquer …​ (donc ils ont besoin de vous !). Il est prêt pour les conteneurs, pour le serverless.

Conclusion

Quand Micronaut a commencé à faire parler de lui, je l’ai trouvé intéressant, mais pas fascinant. Cette présentation, à base de vidéos d’exécution de lignes de commandes, ne m’a pas vraiment convaincue. Mais sans doute que j’ai un à-priori lié à Quarkus …

[DevFest] Il faut sauver le soldat Jenkins

Jenkins-X, est-ce que c’est Jenkins dans K8s ? Non.

Est-ce que c’est une distribution de Jenkins ? Non.

Est-ce que c’est une collection de plugins ? Non.

Est-ce que ça remplace Jenkins ? Non.

 

Jenkins est maintenant hébergé par la CD foundation (filiale de la Linux foundation).

L’approche cloud-native, en général, ça marche bien (même si ça implique le continuous delivery). Mais quand ça foire,c ‘est un peu l’enfer.

En plus, pour faire tourner correctement une appli dans un environnement K8s, il y a un certain nombre d’outils à installer …​ et c’est facilement l’enfer. Heureusement, avec la ligne de commande jx, créer un cluster est aussi facile que lancer jx create cluster gke. Et là, jx va déployer tout un tas d’outils utiles (comme par exemple une container registry, du stockage, un ingress controller, …​). Et surtout, il va connecter cet environnement d’exécution avec un environnement de build dans GitHub.

Il y a d’ailleurs deux modes de déploiement : un mode classique, et un mode « serverless ».

Dans Jenkins-X, la configuration est fournie « as code » et injectée depuis git via prow. Autrement dit, Jenkins-X est une implémentation de gitops, c’est-à-dire une vision infra-as-code. Et pour ça, jenkins-x utilise les pipelines classiques de Jenkins pour mettre à jour l’infrastructure du cluster Kubernetes.

Ca change beaucoup la vie des sysadmins. Parce qu’au lieu d’appliquer des modifications en prod, ils vont pousser l’état de leur cluster et faire de la review de pull-request. Pour appliquer ces pull-request, on fait des promotions d’application dans Jenkins.

Et c’est déja le moment de la démo !

En note préliminaire, mon collègue Logan et moi avions présenté en janvier une conférence à Snowcamp qui traitait partiellement de Jenkins-X, et je ne suis pas peu fier d’affirmer que notre démo ressemblait beaucoup à celle de Nicolas (sans les problèmes techniques terrifiants qu’il a affronté avec classe)

Bref, … Nicolas crée une application Node avec jx create app qui va créer l’application dans GitHub, lui associer un namespace K8s et permettre un déploiement de cette application. Il nous montre ensuite que la création du repository GitHub se fait en deux commits : un premier pour créer le code « basique », et un second pour lui ajouter les éléments propres à Jenkins-X (un chart Helm, des valeurs d’exemple). On passe au vrai développement :

  1. fork GitHub,
  2. modification du code,
  3. création d’une pull-request dans le code applicatif.

Jenkins-X ayant enregistré un web-hook GitHub, il reçoit la notification et lance un build de la pull-request. Et ce build sera déployé dans un namespace spécifique, qui permettra facilement de comparer la pull-request et la prod.

Conclusion

Je pense sincèrement (contrairement à mes collègues ops) que Jenkins-X est une très bonne solution CI-CD à l’heure du cloud. Parce que Jenkins est sans doute le meilleur outil de CI. Et que Jenkins-X y ajoute les éléments nécessaires au fonctionnement dans l’univers Kubernetes.

[DevFest] Doom, Gloom or Loom

En Java, on peut faire de l’impératif ou du réactif. Le code sera assez semblable, mais l’implémentation sera différente. Et ça se verra quand on essayera de débugger. Parce que le code réactif ne sera pas appelé de la même manière.

 

Comment peut-on faire en Java pour avoir une pile d’exécution correcte même avec du code réactif ?

C’est l’objectif du projet loom. Loom rajoute les java.lang.Continuation qui sont « un peu comme » des fonctions qu’on peut suspendre comme on veut grâce à .yield(scope).

On va donc démarrer (et relancer) une continuation avec continuation.run(), et sortir temporairement de la continuation avec .yield(scope).

 

Pourquoi faire ça ?

Historiquement, ce genre de code est géré par les threads. Mais les threads sont lourds. Parce que chaque thread a sa propre stack (qui prend en général 1 Mo de RAM). Et que les threads sont schédulés par l’OS. Ca implique un switch de contexte réalisé par l’OS …​ qui est lent.

Dans les continuations, l n’y a pas de stack, et le switch est réalisé par la JVM, ce qui va plus vite.

Rémi nous fait une démo d’async/await en Kotlin qui est « comme Java, mais sans point-virgule » (mais en vrai plus intéressant que « juste » ça) pour voir les limitations. Ou globalement, si une fonction Kotlin appelle delay(…​), il faut lui ajouter le mot-clé suspend quid evra aussi être mis sur les fonctions appelantes. Ca rappelle bien les throw/catch de Java. D’ailleurs, dans les Fiber Java, on a exactement le même phénomène avec les throws InterruptedException. Au fait, une Fiber, c’est un wrapper de Runnable/Callable qui sera exécuté dans un ExecuorService. C’est très proche d’un thread. Et lorsqu’un Fiber n’a pas de Runnable/Callable à exécuter, il yield(…​) pour que le thread puisse faire autre chose.

Si on regarde l’implémentation Kotlin (ouiii, c’est le moment du bytecode !), le yield est implémenté via un switch (pour le plus dire plus intelligemment une machine à états) dans lequel la branche qui yield envoie une exception contenant le contexte. A noter que le code est généré pour être compilable par javac (parce que les développeurs Kotlin veulent des performances équivalentes à Java). En Java, ça va être différent …​ Quand on yield, on recopie la stack dans la mémoire. Et quand on reschédule la continuation, on recopie la stack dans la stack active.

A noter que l’implémentation de Thread.sleep(…​) a été modifiée pour fonctionner identiquement dans les fiber.

Tout l’intérêt des fiber et des continuations est de permettre à la JVM de gérer ce code asynchrone à notre place. C’est plus performant, et souvent mieux écrit.

Pour résumer …​ Kotlin demande un nouveau compilateur, produit une sacrée de dose de bytecode, mais est rapide ! En plus, on découpe le monde entre code « suspended » et code « classique ». Loom demande une nouvelle VM, n’est pas si rapide (parce que le code est écrit en C peu efficace).

Heureusement, ça ne tue pas la programmation réactive.

Conclusion

J’avais vu sous une autre forme une bonne partie d ecette présentation dans l’université que Rémi a fait avec José Paumard à DevoxxFr. Mais c’est toujours un plaisir d’entendre Rémi nous parler des entrailles de la JVM.

[DevFest] Ecrivez moins de tests, trouvez plus de bugs

Julien écrit du Java depuis 7 ans, du Haskell depuis un an jusqu’en prod, et travaille chez Decathlon.

Et si Julien devait écrire une fonction qui, étant donné une date, détermine si c’est un nouvel an. Ecrire les tests pour cette fonction n’est pas si difficile : le nouvel an tombe à priori toujours le 31 décembre.De la même manière, quand il dit qu’une Citroën C5 a 5 places, tout le monde comprend sans avoir besoin de détails supplémentaires. Parce qu’il s’agit de propriétés.

Donc, est-ce qu’il ne vaudrait pas mieux expliquer comment marche un logiciel à l’aide de propriétés.

 

Quelle est la différence entre un test unitaire et un test de propriété ?

L’entrée est fixée dans un test unitaire, mais aléatoire dans un test de propriétés. Du coup, au lieu d’exécuter le test une fois, on l’exécute de nombreuses fois. Et si on s’intéresse au résultat dans un test unitaire, on s’intéresse aussi au comportement dans le test de propriétés.

Les tests de propriétés ont été imaginés dans les années 80 pour Haskell avec un outil appelé QuickCheck.Qu’ils ont ensuite porté dans une pelletée de langages. Il y a donc des outils de test de propriétés dans la plupart des langages. La suite de la présentation se fera donc avec junit-quickcheck.

Quels types de propriétés peuvent être testés ?

Julien nous donne ici les grands types de « propriétés » testables avec junit-quicheck

L’idempotence

Une fonction est idempotente si sa sortie ne change pas quelquesoit le nombre d’exécution. Le cas d’usage typique est le nettoyage d’entrée utilisateur (nom, numéro de téléphone, adresse, …​).

Et allons-y pour un exemple simple : supprimer les chaînes de caractère vides ou contenant moins de trois lettres.Dans ce test, il suffit simplement de vérifier que le résultat de deux appels successifs à la même méthode est le même.Et junit-quickcheck va générer pour ce test 100 listes de chaînes de caractères avec des textes très variés.

L’invariance

Une fonction est invariante par rapport à une entrée si la valeur de cette entrée ne change pas la valeur de sortie. Par exemple, pour un calcul de nouvel an, l’année n’est pas signifiante. C’est donc un invariant de cette fonction.

Dans le test, c’est facile, on vérifie d’abord que le 31/12 est toujours un réveillon du nouvel an. Ensuite, ça se complique, puisque Julien veut vérifier que les dates qui ne sont pas le 31/12 ne sont pas du nouvel an. Pour ça, il génère des dates aléatoires …​ et forcément, le test échoue (parce qu’il y a dans les dates générées des 31/12).

C’est l’occasion pour Julien de nous montrer que la génération de données pseudo-aléatoire permet de reproduire localement un test qui foirerait sur la CI, en copiant la seed utilisée pour générer les données. Et c’est ensuite l’occasion de nous montrer la méthode assumeThat(…​) qui va exclure les 31/12.

L’analogie

Si vous avez une fonction add(x,y) et une fonction multiply(x, y), on peut considérer que multiply(x, 2) est égal à add(x, x). De la même manière, quand on refactore du code, le résultat du refactoring doit être égal au résultat du code initial.

Allons-y pour un exemple de refactoring !

On a donc une fonction initiale, une fonction refactorée. Et les deux fonctions prennent en paramètre des objets …​ complexes.

L’inverse

Là, tout simplement, si on a une fonction qui donne un résultat, on peut imaginer une autre fonction qui, étant donné le résultat, retourne la valeur initiale. Ca implique aussi que la fonction initiale ne perd pas d’information (sinon on ne peut pas l’inverser correctement). C’est typiquement le cas des expositions HTTP d’un modèle.

Un autre exemple (et c’est celui qu’utilise Julien) est celui du calcul de prix avec ou sans taxe.

Ca permet aussi à Julien de nous montrer comment junit-quickcheck génère les données.

Shrinking

En cas d’erreur, la librairie va aussi réduire l’espace de recherche pour aider le développeur à trouver le cas minimal pour lequel le test échoue.

Retour d’expérience

L’équipe de Julien a mis en place ces tests de propriété après avoir découvert Haskell. En aoutant ces tests pour des classes sur lesquelles ils avaient déja des tests unitaires, ils ont rapidement découvert des bugs …​ qui existaient en prod et n’étaient pas détectés. Ils ont maintenant inversé la balance : ils écrivent des tests de propriétés dans la plupart des cas, et des tests unitaires pour les cas spécifiques. Et en plus, il y a beaucoup moins de code : moins de tests, aucun jeu de données de test, …​

Conclusion

La présentation était très didactique, et pour un concept qui semble évolué, c’est vraiment bien. Et du coup, j’ai logiquement été convaincu par l’approche. Je ne suis toutefois pas sûr que junit-quickcheck soit adapté à junit 5 … Et en plus, il y a une version Rust !

Keepass, c’est l’avenir

Vous savez qu’on vit dans un monde peu sûr.

Et que les mots de passes le sont aussi rarement.

Alors il y a un paquet de sites web qui ont décidé de renforcer leurs mots de passe avec la 2FA. Et ma boîte souhaite l’activer. Je n’étais initialement vraiment pas chaud pour utiliser ça. Parce que bon, ouvrir mon téléphone à chaque fois que je me logge, ça me soûle. Donc j’ai traîné des pieds. Et puis un collègue m’a expliqué qu’il utilisait une Yubikey pour tout ça. C’était un peu mieux, mais pas encore parfait.

Et ce matin, j’ai eu l’illumination : si j’utilise déja Keepass pour stocker mes mots de passe, pourquoi ne pas y ajouter ces mots de passe à usage unique ? Coup de bol, il existe un plugin pour ça ! J’ai donc installé et configuré KeeOTP pour l’utiliser avec GitHub.

Et franchement, le confort est revenu : je fais taper mon login/password par Keepass, et de la même manière ce mot de passe à usage unique. Vraiment, là, c’est bien pratique. Et je vais pouvoir passer d’autres sites en OTP.

To be or not to be serverless

Steve va nous faire un tour d’horizon du serverless, dans le cadre de son tour de France (qui continuera à Lille le mois prochain).

Présentation

Un peu d’histoire

EN 2006, on a commencé à remplacer la virtualisation par le cloud chez AWS (pour lequel Steve a un gros faible). Puis les conteneurs sont apparus et ont conquis le marché, puisqu’aujourd’hui, tout le monde connaît K8s. Puis le serverless est apparu aux environs de 2014.

C’est quoi ?

En serverless, on ne gère pas les serveurs (ça ne veut pas dire qu’ils n’existent pas). En fait, on ne voit pas les serveurs (pas plus qu’on ne les gère). Si on veut distinguer, il y a

  • BaaS : backend as a service. C’est le terme utilisé pour les bases de données accédés comme un service (Mongo Atlas, Aiven) ou des facilités comme Auth0.
  • FaaS : function as a service. C’est la partie à laquelle on va plutôt s’intéresser. On y déploie des fonctions, pour lesquelles on ne gère pas le CPU/la RAM/l’OS. Et évidement, on paye à la requête.

Qu’est-ce qui n’est pas serverless

  • Evidement, le PaaS n’est pas serverless. Par exemple, Heroku n’est pas serverless. Parce que la granularité n’est pas la même …​ et le schéma de pricing n’est évidement pas le même.
  • Les conteneurs ne sont pas du serverless. Parce qu’il va falloir gérer un orchestrateur.
  • NoOps n’est pas non plus du serverless. Effectivement, il n’y a plus d’OS à gérer. Mais il faudra toujours garantir la sécurité. Il faudra également observer le système (ce qui devient beaucoup plus compliqué quand les composants se multiplient).

Qui fait du serverless

AWS évidement. Azure atteint presque le niveau. Il y a également des outils basés sur K8s (OpenFaas). OVH fournit aussi des OVH cloud functions. KNative, OpenWhisk, …​ sont aussi des fournisseurs, moins importants.

Ca veut dire quoi ?

Dans le mode classique, quand une application prend plus de CPU, on rajoute des machines en fonction des besoins. Mais sur chaque machine, on a surprovisionné pour se garder de la marge. Et bien sûr, ça coûte de l’argent.

Du coup, on passe à la conteneurisation. Donc on ajoute des pods dans notre cluster. Mais il y a toujours un moment où on va devoir ajouter un nouveau worker K8s, ce qui est à nouveau de la surprovision …​ Mais grosse (parce que le worker K8s est souvent plus costaud).

En serverless, chaque fonction (c’est-à-dire chaque élément de l’application) sera scalé indépendamment, isolé, et démarré en fonction des demandes. Cette fonction sera aussi arrêtée dès qu’elle n’est plus utilisée. L’intérêt évident, c’est de s’adapter efficacement aux patterns d’utilisation de l’application.

De plus haut

Dans une architecture serverless, il y aura un service externe qui émet des événements. La fonction réagit à cet événement en émettant d’autres événements, ou aussi en stockant des données

Un cas d’utilisation typique est celui de l’application web fortement utilisée de façon temporaire (comme par exemple les applis de vote de téléréalité) : les requêtes seront envoyées sur des fonctions qui vont écrire dans Dynamo pour produire un dashboard accessible worldwide. Un autre cas courant est celui du traitement de fichier en masse : on envoie les fichiers dans S3, ces insertions créent des événements qui sont traités par des fonctions pour écrire dans S3 ou Dynamo. L’IoT est aussi un très bon cas d’usage, en particulier parce que la consommation de ressources va évoluer rapidement dans le temps.

Bénéfices

D’abord on réduit les coûts opérationnels. Pas parce qu’on a supprimé les ops, mais parce qu’on a limité la charge d’opération (en enlevant la gestion des machines). On réduit aussi le coût d’utilisation des BaaS (justement parce que ce sont les fournisseurs de ces backends qui en assurent le fonctionnement). Par ailleurs, la scalabilité devient beaucoup moins coûteuse : pas la peine de penser au déploiement de nouvelles machines, ni à la gestion de la parallélisation du code. L’optimisation du code permet réellement des économies, puisque chaque exécution du code a un coût identifiable. Enfin, l’exécution du code sera plus écologique (parce qu’on consomme évidement moins de ressources).

Contraintes

Avant tout, il ya un vendor-lock-in certain : implémenter du serverless nécessite de s’adapter au fournisseur. Evidement, le métier ne va pas changer. En revanche, toute la partie data/communication sera dépendante du fournisseur.

Par ailleurs, en termes de sécurité, ça n’est pas forcément facile. D’abord parce qu’on sort de la classique DMZ. Ensuite parce chaque fournisseur a sa propré implémentation de politique de sécurité, ce qui nécessite nettement une adaptation.

On ne peut bien sûr plus optimiser sa distribution Linux. En même temps, ça ne sert que dans moins de 1% des cas.

Enfin, les vraies limites sont celles de l’exécution : le provider va limiter la durée d’exécution, le délai de démarrage (et ça, typiquement, à part Quarkus/Micronaut, ça rend le Java inutilisable), la possibilité de faire des tests d’intégration, et même le mode de déploiement/packaging/versioning.

Démo

serverless

Steve va faire la démonstration avec serverless.com (ils ont choisi le bon nom au bon moment). Normalement, l’outil est raisonnablement agnostique (dans sa version payante au moins).

Logan me fait immédiatement la remarque que l’agnosticité du truc est un peu limité : pour créer son appli Python, Steve lance la commande sls create -t aws-python3. Et effectivement, le seul intérêt est que la commande est la même pour aws/azure/…​

Ca crée donc un squelette Python, avec un serverless.yml (et oui, c’est de l’infra as code, donc c’est du YAML) qui décrit comment elle sera appelée.

Et un appel à sls deploy va déployer les éléments nécessaires pour rendre la fonction utilisable depuis AWS.

Et ça marche facilement !

curl -v https://zc0tvoviak.execute-api.eu-west-1.amazonaws.com/dev/users/lille

{
	"message": "Hello Chti JUG",
	"input": {
		"resource": "/users/lille",
		"path": "/users/lille",
		"httpMethod": "GET",
		"headers": {
			"Accept": "*/*",
			"CloudFront-Forwarded-Proto": "https",
			"CloudFront-Is-Desktop-Viewer": "true",
			"CloudFront-Is-Mobile-Viewer": "false",
			"CloudFront-Is-SmartTV-Viewer": "false",
			"CloudFront-Is-Tablet-Viewer": "false",
			"CloudFront-Viewer-Country": "FR",
			"Host": "zc0tvoviak.execute-api.eu-west-1.amazonaws.com",
			"User-Agent": "curl/7.55.1",
			"Via": "1.1 351ae5c6dc020f41490e39fd18b2ac14.cloudfront.net (CloudFront)",
			"X-Amz-Cf-Id": "KkvQqh_2tZ40wbmC2Li9VGTYVg0_HhvCaYQ2gs-yKlLDuMq9OPc9Jg==",
			"X-Amzn-Trace-Id": "Root=1-5ce6dd72-a74747e4c52219abbbb04999",
			"X-Forwarded-For": "165.225.77.153, 70.132.45.71",
			"X-Forwarded-Port": "443",
			"X-Forwarded-Proto": "https"
		},
		"multiValueHeaders": {
			"Accept": ["*/*"],
			"CloudFront-Forwarded-Proto": ["https"],
			"CloudFront-Is-Desktop-Viewer": ["true"],
			"CloudFront-Is-Mobile-Viewer": ["false"],
			"CloudFront-Is-SmartTV-Viewer": ["false"],
			"CloudFront-Is-Tablet-Viewer": ["false"],
			"CloudFront-Viewer-Country": ["FR"],
			"Host": ["zc0tvoviak.execute-api.eu-west-1.amazonaws.com"],
			"User-Agent": ["curl/7.55.1"],
			"Via": ["1.1 351ae5c6dc020f41490e39fd18b2ac14.cloudfront.net (CloudFront)"],
			"X-Amz-Cf-Id": ["KkvQqh_2tZ40wbmC2Li9VGTYVg0_HhvCaYQ2gs-yKlLDuMq9OPc9Jg=="],
			"X-Amzn-Trace-Id": ["Root=1-5ce6dd72-a74747e4c52219abbbb04999"],
			"X-Forwarded-For": ["165.225.77.153, 70.132.45.71"],
			"X-Forwarded-Port": ["443"],
			"X-Forwarded-Proto": ["https"]
		},
		"queryStringParameters": null,
		"multiValueQueryStringParameters": null,
		"pathParameters": null,
		"stageVariables": null,
		"requestContext": {
			"resourceId": "so13c0",
			"resourcePath": "/users/lille",
			"httpMethod": "GET",
			"extendedRequestId": "aJeJ1FFMjoEFg9A=",
			"requestTime": "23/May/2019:17:50:42 +0000",
			"path": "/dev/users/lille",
			"accountId": "961550549303",
			"protocol": "HTTP/1.1",
			"stage": "dev",
			"domainPrefix": "zc0tvoviak",
			"requestTimeEpoch": 1558633842103,
			"requestId": "48707c94-7d83-11e9-ad78-57e2349266a5",
			"identity": {
				"cognitoIdentityPoolId": null,
				"accountId": null,
				"cognitoIdentityId": null,
				"caller": null,
				"sourceIp": "165.225.77.153",
				"principalOrgId": null,
				"accessKey": null,
				"cognitoAuthenticationType": null,
				"cognitoAuthenticationProvider": null,
				"userArn": null,
				"userAgent": "curl/7.55.1",
				"user": null
			},
			"domainName": "zc0tvoviak.execute-api.eu-west-1.amazonaws.com",
			"apiId": "zc0tvoviak"
		},
		"body": null,
		"isBase64Encoded": false
	}
}

Bon, il y a un peu de fuite de données, mais dans l’ensemble ça marche bien.

Chalice

chalice est un framework Python dédié au serverless, qui ressemble beaucoup à flask, mais avec certains décorateurs dédiés. Par contre, comme vous l’aurez vu dans l’url GitHub, c’est purement, totalement, et définitivement dédié à AWS.

Et ça marche tout aussi bien.

Conclusion

Si vous avez déja un outillage de qualité (Docker, K8s), ça n’est pas forcément nécessaire. Par ailleurs, vu la rareté des développeurs du marché du travail, et vues les exigences du serverless, c’est clairement limitant. C’est néanmoins extrêmement enthousiasmant pour les capacités de scalabilité.

A mon avis … on est dans une espèce de mouvement d’avant-garde où on essaye de s’intégrer au mieux dans un cadre financier fourni par les providers de cloud. Est-ce que c’est ‘lavenir , Je n’en sais rien. En revanche, je sais que ça existe depuis … un bon moment, et que ça ne semble pas totallement percer (mis à part des cas bien précis).

Six mois de Rust pour ça ?

Il y a six mois … bon en fait presqu’un an, je commençais une réimplémentation de rss2imap en Rust. Pourquoi ? Parce que Python, c’est lent. Parce que le script avait quelques problèmes que je voulais corriger, et que l’idée de tout avoir dans un script Python 2 me gênait un peu.

Donc je m’y suis mis. Et j’ai découvert des trucs.

D’abord, comme un collègue me l’a expliqué dans un super-chouette midi-conf, effectivement, en Rust, il y a le borrow-checker, il y a la durée de vie (et sérieusement, c’est très cool en terme de gestion de mémoire). Mais parfois … c’est chiant. Et comme mon projet n’a pas des ambitions de performance extrêmes, ben je peux faire des .clone() quand j’ai des problèmes d’ownership. Une fois que j’ai assumé le fait que ce ne sera pas totalement ultra-optimisé, eh ben d’un coup, mes problèmes se sont simplifiés. Et franchement, c’estd evenu très cool.

Ensuite, j’ai vraiment, mais alors vraiment apprécié la qualité de l’interface du compilateur : les messages d’erreur sont hyper clairs et aident vraiment à ce qu’ils soient corrigés. C’est bien plus agréable même que ce que fait le compilateur Java. Et c’est un truc incroyable, pour un langage aussi peu approchable, que d’avoir réussi à produire cette qualité d’interaction.

De la même manière, l’écosystème est bien plus garni que ce que j’ai pu croire au début : pour chaque problème, il y a une dépendance. J’ai l’impression d’être dans le monde Java ! Et (même si ça déplaira à Nicolas – l’autre), je trouve que ça fait gagner un temps fou. Par contre, il ya  une petite différence avec Java : j’ai beau avoir un gros paquet de dépendances, mon exécutable Rust fait, sur la plateforme la plus lourde, 9,97 Mo. Ca n’est … pas si lourd que ça, je trouve … Et puis crates.io, c’est génial : ils fournissent un graphe des dépendances téléchargées, affichent le README du repository GitHub, et tout un tas d’autres trucs tellement cool. C’est vraiment un super outil de gestion des dépendances.

Evidement, tout ça ne serait rien sans un outil de build approprié. Et là, de la même manière, cargo, c’est fou tellement c’est bien. C’est facile à utiliser, le format toml (dont je pensais qu’il me contraindrait un peu trop) s’est révélé bien pratique, et le mode de gestion des plugins est un sens plus sympa que ce qu’on peut trouver dans Maven.

Au passage, j’ai bien galéré avant de trouver une solution de compilation multi-plateforme. Et cette semaine, j’ai mis en place Travis-CI. Et en deux jours, je suis passé de rien au déploiement automatique de releases pour 9 environnements différents (Linux – pour x86 et Raspberry, Windows, MacOS),  directement dans les releases GitHub. J’ai certes deux ou trois détails à peaufiner (comme par exemple la génération d’un bon changelog dans les releases), mais dans l’ensemble, ça marche furieusement bien.

Donc effectivement, j’ai progressé bien plus lentement que c que j’imaginais au début (parce que j’y ai consacré somme toute très peu de temps). Mais j’ai une confiance assez forte dans le code que j’ai écrit. Et c’est vraiment cool. Ca me donne envie d’en faire plus … bien plus (autrement dit d’aller chercher la mission magique où je ferai du Rust en vrai).