Mon passage à la ScalaIO 2017
La semaine dernière, j’ai eu le plaisir de gagner une place pour la ScalaIO 2017 qui a eu lieu à Lyon ces 2 et 3 Novembre. Ok, ça me laisse une semaine pour m’organiser entre la famille, ma boite (Zenika) et mon client (Norauto). Mais comme j’ai une épouse en or, une boîte en or et un client en or, qui plus est en est sponsor, aucun souci et tous les détails ont été réglés en… une journée !
Trève de bavardage, la ScalaIO c’est quoi ?
C’est pas moins de 66 conférences, dans un format allant de 15 à 50 minutes, réparties sur 3 salles et un amphi pendant 2 jours. Ces conférences ont toutes un rapport de près ou de loin avec la programmation fonctionnelle, Scala et/ou le traitement de flux de données (streaming).
Cette année l’association à but non lucratif a réussi à s’offrir les services d’une société de couverture médiatique afin de filmer toutes les confs. Utile pour ceux qui n’ont pas pu se déplacer ou encore pour ceux qui, comme moi, n’ont pas le don d’ubiquité. J’aurai donc la chance de visionner les confs, que j’ai malheureusement dû choisir de ne pas voir, sur la chaîne Youtube dont je mettrai le lien en bas de page quand il sera diffusé.
Il est temps de choisir son programme dans ce calendrier : https://scala.io/schedule.html. Chose pas évidente à faire car le détail des talks n’est pas consultable directement depuis le calendrier, cela m’a donc pris un peu de temps pour faire mon choix.
Je vais me concentrer sur les quelques confs qui m’ont le plus intéressé.
Keynote d’ouverture
C’est Dean Wampler (auteur du livre Programming Scala) que nous avons l’honneur d’accueillir pour ce talk intitulé “Stream all the Things!”.
Tout est dans le titre, je pourrais presque ne pas aller plus loin, mais je ne vais pas vous laisser sur votre faim. Le message c’est : on peut tout streamer et traiter les données au fil de l’eau mais en a-t-on vraiment besoin ?
Nous devons choisir la solution la plus adaptée à notre besoin (ou à celui du métier) qui pourra s’exprimer sur 4 axes :
- Temps de réponses
- Volumétrie
- Type de traitement
- Mode de traitement
Pour ce qui est du temps de réponse, les solutions peuvent aller du Hardware/Software conçu spécifiquement pour notre besoin en prenant soin de supprimer toute source de latence (pas de JVM, code au plus proche de la machine, infra réseau, etc.) pour les solutions devant répondre en temps réel, au batchs périodiques là où des temps de réponse de plus de quelques minutes sont acceptables.
En ce qui concerne la volumétrie, là aussi, tout est une histoire de positionnement du curseur pour choisir le bon outil. On va du traitement “un à la fois”, comme le traitement de requêtes REST, au traitement par lot (bulk) en passant par les traitements parallélisés.
Quant au type de traitement, on peut citer des jobs ETL (Extract, Transform and Load), du dataflow, du Machine Learning.
Enfin, pour le mode de traitement, il est nécessaire de savoir si nous voulons un traitement par lot (bulk) ou un traitement individuel de chaque donnée.
On enchaîne avec un tour d’horizon des solutions actuellement sur le marché (Akka, Flink, Spark, Kafka) qui ont le vent en poupe pour répondre à ces différents besoins et leur intégration avec d’autres outils de gestion de données (Base de données, Kafka, Système de fichier, S3).
- Apache Flink : fait pour du traitement de gros volume avec un faible temps de réponse
- Akka Streams : permet des traitements un peu plus complexe des données
- Kafka Streams : à privilégier dans le cas d’un interfaçage avec Kafka et idéal pour faire de l’ETL et de l’agrégation de données
- Spark Streaming : plus adapté dans un modèle mini-batch et est encore en développement
- Spark Batch : solution de batching un peu plus classique
On finit par une comparaison rapide entre une application data-centric et une application à base d’architecture microservice. On arrive à la conclusion que nos applications à base de microservices vont devenir de plus en plus data-centric. Dean prend l’exemple de twitter qui était une application 3-tiers classiques mais qui est malheureusement devenu une vraie usine à gaz.
Le compte twitter du speaker : @deanwampler
Les slides de la conf : Github
Son livre : https://info.lightbend.com/COLL-20XX-Fast-Data-Architectures-for-Streaming-Apps_LP.html
Vidéo de la conf : A venir
Introduction à Akka-Streams
Présenté par Marc Karassev qui profite de 10 mois de pratique sur Akka-Streams pour nous faire part de ses découvertes. Il est également membre du Lyon Scala User Group.
Ok, Akka-Streams est une librairie de traitement de données en temps réel permettant le traitement atomique (one at a time) ou par lot (bulk) des données ainsi que les traitements asynchrones (pour le découplage) et intégrant un système de back-pressure. Cette librairie implémente les Reactive Streams. Il est basé sur le design pattern Publish/Subscribe.
Marc vulgarise très bien les différents composants d’un stream que sont les Source, Flow et Sink qui sont pour lui respectivement les Robinet, Canalisation et Siphon de notre plomberie à la maison. Nous voilà dans la peau de Mario 😀
Donc en gros, on fabrique une Source de données à base de ce que l’on veut. Ça peut être un fichier sur le filesystem, une base de données, un topic Kafka, un bucket S3, ou tout autre source de données. Ensuite on fabrique un Flow qui représente le traitement ou la transformation que l’on veut effectuer sur chaque élément de notre flux de données (la Source). Puis on finit par définir le Sink qui sera le traitement de sorti du flux après le traitement (enregistrement en base de données, écriture dans un topic Kafka, etc.).
On part sur une démo très simpliste pour bien comprendre le fonctionnement. L’exemple est si basique (prendre une liste d’entiers et afficher chaque entier doublé) qu’on en vient même à se demander ce que Akka-Streams peut bien apporter de plus que du map-reduce en Scala natif. Mais on comprendra plus tard grâce notamment à ce slide qu’on peut effectivement chaîner plein de Flow et de Shape différents pour construire un Graph.
Mais avant ça, on va se pencher un peu sur :
- Ce qu’est un Graph : composition de Sources, FLows et Sinks
- Ce qu’est un Mat : c’est une Materialization, le concept le plus difficile à appréhender
- Ce qu’est un RunnableGraph : c’est un graph qu’on pourra lancer dans le future
- Ce qu’est une Shape : comme son nom l’indique c’est une forme quelconque, il existe par exemple les FanInShape2 (qui prend 2 entrées et 1 sortie) et FanOutShape2 (qui prend 1 entrée et 2 sorties)
Toutes ces notions ont leur pendant en terme d’objet, ils sont donc facilement réutilisables et re-composables à volonté grâce à l’immutabilité. En effet, une fois l’objet instancié, rien ne peut le modifier, on peut donc l’utiliser dans la définition de plusieurs Graph.
Il est possible de fabriquer ses propres Flow custom pour des traitements non triviaux.
Au niveau de l’asynchronisme, Marc nous fait une démo des effets que nous pouvons observer sur un traitement Akka-Streams non protégé par le mécanisme de Back-pressure.
Deux cas d’utilisation :
- Le Publisher publie trop de message : 2 possibilités, soit on ne fait rien et dans ce cas le consommateur tombera en OutOfMemoryError, soit on tente de bufferiser les données, mais dans ce cas, certains messages seront droppés et donc perdus à tout jamais.
- Le Subscriber consomme trop de messages : il risque de surcharger le Publisher en le noyant sous les sollicitations réseau.
Mais ces deux cas coexistent et aucun ne peut être considéré comme prédominant. Il faut donc pouvoir gérer les deux cas simultanément.
Marc nous explique donc rapidement le principe du Pull based back pressure et nous fait une démonstration finale en guise de conclusion :
Talk très instructif et très concret avec des démos sur Akka-Streams que je ne connaissait pas avant de venir, c’est une bonne surprise pour moi.
Le compte twitter du speaker : @KarassevMarc
Lien vers les Slides : Google Docs
Lien vers le code source des différentes démos : https://github.com/markarasev/akka-stream-demo
Vidéo de la conf : A venir
Monix : Une alternative à Akka-Streams ?
Ce talk me semble complémentaire du précédent. Il est assuré par Bounkong KHAMPHOUSONE.
Monix est, à l’instar de Akka-Streams, une librairie de traitement de flux de données.
Là où Akka-Streams implémente Reactive Streams, Monix fait le choix de repartir du design pattern Observable/Observer en définissant les méthodes onNext(), onComplete() et on Error() qui permettent respectivement de lire un nouvel élément, notifier de la fin du flux et notifier de la survenue d’une erreur.
On arrive sur une suite de comparaisons brutes de fonderie et complètement objectives à base de snippets de code. Ne “connaissant” Akka-Streams que depuis environ une heure, il m’est difficile de constater les différences (qui ont peut-être sautées aux yeux des initiés).
Néanmoins, de ce que j’en ai vu (car un peu rapide), c’est quasiment la même chose.
J’aurai donc préféré avoir un avis du speaker sur les différences entre les deux librairies, leurs avantages et leurs inconvénients respectifs.
Je pense que ce slide reprend tout ce qu’il faut retenir de ce talk :
Suite à la question “Et toi, tu préfère laquelle de ces deux lib ?”, Bounkong nous confie qu’il préfère Akka-Stream car il est de plus bas niveau même si Monix offre plus de Transformer out-of-the box que Akka-Streams. Un argument en faveur de Monix est de supporter l’utilisation de Cats et de ScalaZ.
Le compte twitter du speaker : @tibounk
Dépôt github de la conf : Github
Vidéo de la conf : A venir
Mort au boilerplate avec Scala-meta
Petit talk de 20 min animé par Damien GOUYETTE qui utilise des macro depuis maintenant 5 ans et en produit depuis 3 ans.
Scala-meta permet de définir des annotations permettant l’exécution de macro de génération de code dit boilerplate au moment de la compilation (sbt clean compile). Le boilerplate est le nom donné au code qui prend du temps à un développeur mais qui n’apporte aucune valeur à l’application. On peut prendre l’exemple d’un toString() ou de tous les getter-setter (mauvais exemple avec Scala qui génère déjà tout ça directement pour les case classes).
Mais c’est quand même l’exemple qui sera utilisé ici.
On commence avec un peu de vocabulaire nous permettant d’appréhender la suite sans être perdu :
- AST (Abstract Syntax Tree) : représentation du programme (voir astexplorer)
- Symbole : tout ce qui peut être défini (Classes, Terms and Literals)
Le Symbole Defn.Class porte toutes les informations de la classe à laquelle il fait référence. Cela nous permet de récupérer les infos d’une classe : Constructeurs, Méthodes, Paramètres, Membres, etc.
Ensuite, grâce à ça, une macro est capable de déconstruire une classe pour la reconstruire ensuite mais avec du code en plus en fonction du contenu de la classe de départ… Ça me fait quand même vachement penser à de l’introspection en Java cette histoire.
Pour utiliser une Macro c’est plutôt facile, il suffit d’annoter la classe qu’on souhaite “augmenter” :
@ShowMacro(constructorParams)
case class Name(firstName: String, lastName: String)
On pourrait donc, dans ce cas, créer une macro qui se charge de générer le code de la méthode macroShow(). Mais le développement d’une macro n’est pas si trivial. Il faut donc que le temps passé à écrire la macro soit rentabilisé par le nombre de classes concernées par cette macro (annotation).
Heureusement, il existe certains facilitateurs pour simplifier la déconstruction/reconstruction des symboles : les Quasiquotes par exemple. L’exemple donné par Damien est quand même super flagrant : q"""println("hello world")""" équivaut à Term(Term.Name("println"), Seq(Lit.String("hello world")))
Les Quasiquotes me font un peu penser à l’interpolation de chaine de caractère (s"$val1, $val2"). Pas étonnant puisque, après quelques recherches, il s’avèrent que les Quasiquotes sont qualifiés d’« une autre utilisation de l’interpolation de chaîne » dans la doc scala.
Par contre, il faut absolument :
- placer les macros dans un module bien séparé du code de l’application
- compiler les macros avant de compiler le code de l’application (project.dependsOn(…))
- ajouter la dépendance à org.scalameta::scalameta::1.8.0 (en provided)
Le compte twitter du speaker : @cestpasdur
Lien vers les slides : Google Docs
Vidéo de la conf : A venir
Kleisli, un amis qui vous veut du bien.
Séance de live-coding pleine d’enseignements, animé par Xavier Bucchiotty et son collègue Tristan Soullz tous deux de la société Teads qui se charge de sélectionner en temps réel du contenu publicitaire en fonction de différents critères comme le contenu de la page host, la localisation de l’utilisateur, sa langue, son device, etc.
Ils nous font donc un retour d’expérience sur leur utilisation du langage fonctionnel qu’est Scala pour résoudre leur problématique. Et je salue l’initiative, c’est le premier talk que je croise qui part d’un cas ultra concret en live-coding pour montrer leur cheminement.
On commence tout simplement avec le step 1, pendant lequel on définit une librairie de vérification de règles pour savoir si un contenu publicitaire est éligible ou non à se faire héberger par une page web en cours de visionnage par un internaute.
On définit 2 règles, une sur le device utilisé et une sur le pays :
def deviceRule(device: Device): Rule[Ad] = {
ad => ad.device == device
}
def countryRule(country: Country): Rule[Ad] = {
ad => ad.country == country
}
Puis le moteur de vérification :
def run[T](rules: List[Rule[T]], t: T): ExecutionResult = {
rules.forall(rule => rule(t))
}
Puis on demande une évaluation sur la liste des règles pour savoir si l’ensemble des règles sont satisfaites ou non :
run(List(countryRule("FR"), deviceRule("Mobile")), ad)
Ultra Simpliste !
On passe au step 2. Si une des règles échoue, on aimerait savoir pourquoi la vérification dans son ensemble à échouée. Il faudra passer par un Either qui permet de gérer le failure et d’en remonter la raison au résultat final.
Les règles deviennent :
def deviceRule(device: Device): Rule[Ad] = {
ad => Either.cond(ad.device == device, ad, "Not good device")
}
def countryRule(country: Country): Rule[Ad] = {
ad => Either.cond(ad.country == country, ad, "Not good country")
}
Et le moteur de vérification devient :
def run[T](rules: List[Rule[T]], t: T): ExecutionResult[T] = {
rules.foldLeft(Right(t): ExecutionResult[T]) {
case (Right(_), rule) => rule(t)
case (left, _) => left
}
}
On complexifie un tout petit peu le code pour un gros gain.
Step 3, au fil de l’évolution du besoin, on constate que la vérification d’une règle va nécessiter un appel IO (un accès base de données, un appel à une API de géolocalisation, etc.). Il faut donc pouvoir passer par des Future pour gérer le côté asynchrone des appels IO. On ne se pose pas trop de question et on wrap tout dans des Future.
OK, c’est super, tout fonctionne mais… c’est pas performant !
Entre le step 1 et le step 3 on vient de complexifier la composition de règles à cause des Future et également de diviser par environ 400 le temps de réponse alors que nous n’avons rajouté que les Either et les Future. Comment faire pour parvenir à régler ce problème de performance. Le constat semble que ce soit les Future qui consomment le plus. Il serait donc judicieux de séparer les règles dont on sait qu’elles n’ont pas besoin de Future et celles pour lesquelles l’utilisation est indispensable. Il serait également judicieux de les positionner en fin de traitement pour en faire l’économie si une des règles sans Future échoue.
Je ne vais pas refaire toute la prez, déjà parce que ce serait trop long, mais aussi parce que je n’ai pas compris comment ils ont fini par arriver au résultat de leur réflexion : l’utilisation de Monad puis de Kleisli (Classe de la librairie Cats). Je vais donc devoir me replonger dans cette prez ainsi que le code source pour en comprendre les tenants et aboutissants. Toujours est-il que la comparaison des benchmarks sur les différentes étapes est parlante.
Ce qui m’a énormément plu dans ce talk c’est de voir du concret, de voir l’évolution du besoin ainsi que le cheminement qui amènent à utiliser des concepts aussi abstrait, pour moi, que les Monoid, Monade ou Semigroup. Ils ont réussi à analyser leur problématique, et recoller les morceaux avec des concepts qu’ils avaient déjà croisés. Tristan avoue quand même avoir eut peur que l’ajout de ces notions complexes plomberaient encore plus les performances, ce qui n’a, à sa grande surprise, pas était le cas.
Les Monade, Monoid, MonoidK et autre Functor sont des concepts que je suis encore très loin de bien comprendre. J’ai toujours eu ce sentiment que Scala, et la programmation fonctionnelle en général, était particulièrement (et presque uniquement) adaptée à la résolution de problèmes mathématiques. Je suis heureux aujourd’hui de constater que j’ai, en partie, tort. Cela me donne envie de maîtriser ces concepts pour faire mes choix d’implémentation en pleine connaissance de cause.
Il est tout à fait possible de résoudre des problèmes de la vrai vie avec ces concepts une fois qu’ils sont compris, maîtrisés et correctement appliqués comme cela est fait par Xavier et Tristan chez Teads.
En deux mot, Merci et Bravo !
Le compte twitter des speakers : @xbucchiotty & @TristanSoullz
Lien vers les slides : Google Docs
Vidéo de la conf : A venir
Crash course in Category Theory
Cette introduction nous est proposée par Bartosz MILEWSKI, un speaker au poil, pédagogue à souhait, super empathique, avec un débit de parole maîtrisé et sans accent. Il prend son temps pour expliquer ce qui est, pour moi, le plus compliqué à appréhender : La théorie des catégories.
Son but n’est clairement pas de parcourir le plus de concepts possible en moins de temps possible. Il a même dépassé son temps de parole de plus de 30 minutes parce que tout le monde a refusé qu’il s’arrête en si bon chemin.
Malgré la complexité du sujet abordé, je pense que c’est le talk qui m’a le plus passionné. On (re)part de zéro et on avance progressivement en faisant de l’analogie entre la Théorie des Ensembles (Set Theory) et la Théorie des Catégories (Category Theory) en vulgarisant un maximum avec des ronds et des flèches.
J’ai vraiment compris certaines choses comme les Monoids, les Monades, les Functor. Je ne saurai pas les restituer ni m’en servir mais j’ai réellement compris et ça c’était pas gagné. Je pense qu’il va me falloir relire les slides de cette prez, même si elles ne représentent en fait que 20% du talk, pour bien tout digérer.
Le compte twitter du speaker : @BartoszMilewski
Lien vers les slides : CrashCourse ScalaIO.pdf
Vidéo de la conf : A venir
Conclusion
Deux jours de conférences super enrichissantes avec des talks et des speakers tous plus intéressants les uns que les autres. Le choix des conférences à suivre fut un vrai supplice par moment. Douleur vite effacée par la couverture médiatique assurée par iStream qui nous permettra de voir ou revoir certains talks que nous aurions pu manquer ou choisi de ne pas suivre au profit d’un autre.
J’espère avoir l’occasion d’y retourner l’année prochaine. Vivement la ScalaIO 2018 !
Je finirai sur un petit tour d’horizon de ce que m’a apporté cette ScalaIO 2017 en plus des différents talks que j’ai déjà détaillés.
- Des séances de refactoring aussi bien en live-coding qu’en présentation plus classique – la programmation fonctionnelle ca ne signifie pas penser « fonction » au sens « méthode », mais penser fonctionnelle pure (immutablité, effet de bord, typage, etc.). Ce n’est pas trivial, c’est une perspective complètement différente, un mode de pensée différent et tous les speakers se sont accordés à dire que le temps d’adaptation quand on vient d’un monde impératif comme Java n’est clairement pas négligeable.
- Des talks incompréhensibles, non pas par la faute de speakers de mauvaise qualité mais par la mienne et mon manque de maturité sur le sujet – je ne pratique Scala, et la programmation fonctionnelle plus généralement, que depuis très peu de temps (un mois environ) et certains concepts, que je n’avais pas encore croisé, étaient nécessaires à la compréhension des sujets présentés. Maintenant je sais où sont mes lacunes, à moi de les combler.
- Du networking – j’y ai croisé d’anciens collègues que je n’avais pas revu depuis 6 ans. J’ai pu aussi faire connaissance, durant la community party, avec certains organisateurs de la ScalaIO à qui je pourrais apporter mon aide pour les années suivantes. J’y ai également croisé Valentin Kasas, speaker lors du dernier Ch’ti JUG sur Scala et Philippe Charrière, speaker lors d’un GDG Lille sur la programmation fonctionnelle.
De manière plus globale, j’ai la sensation que la programmation fonctionnelle prendra de plus en plus de place dans notre métier, et ce quelque soit le langage, dans un avenir relativement proche.