Build 2019 – Jour 2

Écrire un article résumant une journée complète de conférences qui n’aurait pas l’air de sauter du coq à l’âne n’est pas une tâche facile. Pourtant, je pense que l’information recueillie aujourd’hui est très pertinente. C’est pourquoi je vous propose des résumés des différentes présentations intéressantes auxquelles j’ai assisté aujourd’hui à la deuxième journée de la conférence Build 2019 de Microsoft.

Productivité avec Visual Studio 2019

20190507_153018 (2).jpg

Le but n’était pas de vous énumérer toutes les améliorations de VS 2019 comme une liste d’épicerie, je mentionnerai seulement celles qui m’apparaissent plus pertinentes. Pour la liste complète, vous pouvez consulter les notes de livraison.

Depuis la version 2017, Visual Studio améliore sans cesse ses performances. Il gère de mieux en mieux les solutions de grande taille et la nouvelle expérience de démarrage ainsi que les filtres de solutions sont dans cette lignée.

Les Code lens sont maintenant offertes dans la version Community et sont aussi extensibles. Il est maintenant possible de créer ses propres Code lens!!!

On peut aussi voir ou éditer les fichiers de projet avec un banal double-clique. Know it, learn it, love it!

Aperçu de certaines fonctions

Dans les versions Preview, certaines fonctions sont disponibles pour recueillir le feedback. L’ajout de l’auto-complétion dans les chaînes de caractères RegEx a notamment retenu mon attention.

Aussi, IntelliCode a été livré formellement, mais certaines nouvelles fonctionnalités sont déjà en route.

Pour rappel, IntelliCode permet d’utiliser le Machine Learning pour améliorer l’expérience des développeurs. Entre autres, les éléments d’intellisense les plus populaires, dans le contexte de notre curseur, apparaîssent en premier.

Il sera prochainement possible d’entraîner le modèle d’IntelliCode avec notre propre code pour qu’il soit en mesure de nous aider avec les classes que nous développons nous-mêmes. L’exécution se fait localement pour plus de confidentialité. Il sera également possible de partager le modèle ainsi généré avec nos collègues au besoin.

Toute les équipes finissent par avoir une convention de style pour le code. Le fichier .editorconfig permet aux utilisateurs de VS de personnaliser cette expérience. Toutefois, il peut être difficile de répertorier toutes ces conventions. IntelliCode viendra à la rescousse et permettra de générer un fichier .editorconfig basé sur les conventions informelles de notre base de code.

JAM Stack + Azure Functions

Cette présentation était constituée d’une série de démos pour montrer l’intégration des différents outils de la JAM Stack telle que vue par John Papa et Matt Hernandez. Cet acronyme sert à désigner un ensemble de technologies qui fonctionnent bien ensembles. On parle ici de Javascript, APIs et Markup.

20190507_083546

Les démos ont servi à montrer comment il était facile de développer une application Angular dans VS Code qui repose sur un back-end Azure Functions écrit en TypeScript dans le même répertoire que l’application Angular. En plus des capacités formidables d’édition web, VS Code est un excellent outil pour développer et déboguer les Azure Functions localement sur notre poste. La démo utilisait NodeJS pour l’exécution locale et les présentateurs ont montré comment démarrer l’Azure Function et l’application web dans le navigateur en même temps en appuyant sur F5. L’utlisation du fichier launch.json étant la clé.

Les présentateurs ont aussi utilisé les extensions VS Code pour déployer leur application dans Azure directement, ce qui procure une expérience très intuitive et une boucle de rétroaction rapide. Ils ont terminés en montrant comment Azure DevOps pouvait être utilisé simplement pour assembler des artéfacts de déploiement et ensuite les déployer vers les ressources Azure. Dans le pipeline de build, l’utilisation des tâches npm permet de réutiliser le même outillage que l’environnement local pour la partie Angular. Bref, l’intégration est très intéressante et semble assez mature.

Windows Subsystem for Linux v2 (WSL2)

Microsoft a annoncé la nouvelle version de son Windows Subsystem for Linux : WSL 2. Les deux principaux avantages sont la performance et la compatibilité avec les fonctionnalités natives des distributions Linux. Le tout repose sur l’utilisation de la virtualisation légère qu’ils appellent Lightweight utility VM qui est rendu possible avec les travaux qui ont été faits pour le support des conteneurs dans Windows. Une VM Linux est donc démarrée par la couche WSL 2 qui s’occupe d’abstraire les détails pour nous. C’est pourquoi Microsoft distribuera les versions du noyau Linux via les Windows Update. Qui aurait dit que cela se produirait un jour? Toutes les distribution de Linux seront exécutées dans une seule VM en utilisant les fonctionnalités natives qui soutiennent les technologies des conteneurs sous Linux. Il est donc possible d’accéder aux fichiers Windows à partir de WSL et aussi d’accéder aux fichiers de la VM Linux à partir de Windows comme si de rien n’était. Les avancées de WSL 2 permettent aussi l’utilisation de Docker dans WSL directement.

20190507_104059.jpg

À la fin de la présentation, un rappel a été fait sur le mode « remote » de VS Code. Ce dernier permet d’exécuter l’interface utilisateur sous windows, mais de déléguer l’ensemble de l’exécution au WSL. Pour en savoir plus

Développement de microservices avec AKS, GitHub et Azure Pipelines

Microsoft est sérieux pour ce qui est de l’intégration de Kubernetes dans ses différentes plateformes. En plus de l’expérience du portail Azure en constante amélioration, les outils de développement demeurent une priorité. Dans une présentation comportant plusieurs démos, nous avons vu à quel point il est facile d’itérer rapidement dans un contexte AKS avec Visual Studio. Le débogage d’un conteneur dans AKS est possible directement à partir de notre IDE favori. Une possibilité incroyable est aussi de pouvoir modifier notre application sans avoir à regénérer notre image de conteneur et à la publier dans AKS. L’exécution se fait localement et AKS route les appels vers notre machine de développement, ce qui accélère grandement le développement. Tout cela est rendu possible grâce à DevSpaces : des espaces de développement isolés dans un cluster partagé. Un développeur peut alors modifier un composant et le tester sans impact sur les autres personnes qui effectuent des essais dans le même environnement.

20190507_130315

Des outils de ligne de commande ont été ajoutés à la CLI Azure pour faciliter le démarrage. Par exemple,  pour configurer notre espace, les lignes de commande suivantes sont suffisantes :

az aks use-dev-spaces -g [resourceGroupName] -n [clusterName]

az azds prep

az azds up

Il est donc facile de démarrer rapidement avec AKS dans Azure.

Des ajouts intéressants ont été fait dans Azure DevOps qui permettent de voir nos environnements Kubernetes directement dans le portail Azure DevOps.

Screenshot_20190507-225111_Chrome (2).jpg

Toujours dans le but d’accélérer le démarrage avec AKS, Azure DevOps fournit maintenant un assistant de création de pipeline pour AKS. Ce dernier automatise la création des fichiers azure-pipelines.yaml et des manifestes requis par Kubernetes pour déployer notre application. Le tout est alors archivé dans notre dépôt de code pour être modifié comme du code par la suite.

Un scénario qui est vraiment intéressant avec tous ces ajouts est celui de pouvoir effectuer des tests manuels dans un espace DevSpaces AKS basé sur une Pull Request. En détectant qu’un pipeline de déploiement a été déclenché à partir d’une Pull Request, on peut concevoir notre pipeline de sorte qu’il crée un espace DevSpaces à la volée pour nos tests. Toutes ces étapes sont intégrées également dans GitHub.

Conclusion

En plus des nouveautés mentionnées plus haut, une foule d’autres sujets ont capté mon attention comme l’annonce de l’API Management d’Azure qui permettra de fédérer des gateways on-prem avec Azure à l’aide de conteneurs.

De plus, les discussions avec les équipes des différentes produits sont très intéressantes pour obtenir des réponses détaillées sur une foule de sujets.

Encore une autre belle journée en perspective demain. Au menu, Web Assembly et plus!

 

Faciliter le passage à l’architecture microservices

Microservices

Image : http://comunytek.com/en/introduction-to-microservices/

Quand on regarde les tendances de l’industrie actuelle, il est difficile de ne pas entendre parler d’infonuagique (cloud) et des microservices. Depuis quelques années, des leaders comme Netflix, LinkedIn, Twitter, Google, Amazon et Microsoft se sont imposés dans ces domaines et ont bien voulu partager leur savoir-faire en publiant des plateformes open-source et des articles. Bien que l’univers du cloud et des microservices requiert des changements dans la manière de gérer les technologies et les infrastructures, il faut aussi adapter l’architecture logicielle pour aborder ces nouveaux paradigmes correctement.

Dans le contexte d’une application existante, le passage au cloud et à l’architecture microservices doit se faire de manière itérative. Il n’est pas judicieux de tout changer en même temps, car il devient impossible de mesurer si l’investissement aura eu les bénéfices escomptés. Alors, par où commencer?

Selon Sam Newman, la bonne pratique est de commencer par modifier la structure du code petit à petit afin de tendre vers l’indépendance des déploiements. Qui dit indépendance des déploiement dit évidemment découplage. Alors on doit commencer à créer des plus petites unités de déploiement dans notre logiciel qui soient découplées les unes des autres. Ce genre de changement peut se faire sans contrainte d’infrastructure et sans impact sur la sécurité. Voyons donc en détail comment on peut se préparer à l’approche microservice dès aujourd’hui.

Définir les frontières d’un service

Le principal défi au niveau du design dans l’architecture microservice est de déterminer les frontières d’un service. On sait que le monolithe n’est pas une bonne idée. On sait aussi qu’il n’est peut-être pas optimal de créer un service par classe de logique d’affaire (aussi appelé classe du domaine). Alors on doit trouver le juste milieu. Mais comment? Il n’y a pas de réponse toute faite à ce sujet, mais l’élément qui revient le plus souvent dans la littérature est d’utiliser le concept du Bounded Context tiré du Domain Driven Design. Celui-ci serait trop long à décrire dans cet article, mais l’idée est de s’assurer que notre service est lié à une seule fonction d’affaire. Par exemple, dans un site de commerce en ligne, les fonctions de gestion des commandes et de gestion des livraisons ne devraient pas faire partie du même service. En effet, pour faire un lien avec le Single-Responsibility-Principle (SRP) des principes SOLID, un service ne devrait avoir qu’une seule raison de changer. Normalement, les fonctions d’affaires tendent à rester relativement stables dans une entreprise. C’est sans doute l’élément qui changera le moins souvent. C’est donc une bonne idée de créer des services qui sont alignés avec ces fonctions d’affaires afin de minimiser les changements de frontières qui pourraient survenir. Toutefois, la connaissance de notre domaine d’affaire peut évoluer et amener à faire des changements dans la topologie des services. Le découpage et le niveau de découplage du code peut rendre ces changements très pénibles ou très faciles. Il est donc important de s’y attarder.

Le problème avec le découpage en couche

Pendant plusieurs années, l’architecture n-tiers proposait de séparer les composants logiciels par couche applicative.

N-Tiers

Cette façon de faire permet d’ordonner et de structurer le code pour séparer les responsabilités. Toutefois, elle n’est pas orientée vers les tests automatisés, car elle rend difficile la substitution et elle n’encourage pas le SRP en n’étant pas alignée sur les fonctions d’affaires. Il est aussi difficile d’éviter de faire un gros spaghetti et de comprendre d’un coup d’œil quel est le but de l’application. Lorsqu’un développeur veut créer un service avec une ou plusieurs classes de logique d’affaires, il doit effectuer une analyse des dépendances entre les classes afin de mesurer l’impact du changement. Cette approche rend plus difficile l’évolution de l’architecture.

Cohésion forte, couplage faible

En SOA, c’est sans doute le principe qui fait le plus consensus. Un service doit rassembler des opérations à forte cohésion. Tous les éléments d’un même sujet devraient être traités dans le même service. De plus, chaque service devrait être faiblement couplé avec ses pairs. Ainsi, on tend à diminuer les vagues de changements. C’est ce principe que reprend Uncle Bob dans son livre Clean Architecture. Il applique ce principe non seulement au niveau des services, mais aussi au niveau des composants. Il énonce certains principes permettant d’évaluer la cohésion et le couplage afin de créer les bons regroupements dans le code.

En DDD, le principe de cohésion forte encourage à regrouper les éléments du domaine qui sont intimement liés en agrégats. Toutefois, le couplage entre ces derniers doit être évité. On doit également éviter le couplage avec les technologies. L’interface utilisateur ou le moteur de base de données utilisé doivent être traités comme des détails d’implémentation qui sont jetables. Ce qui va perdurer dans le temps, comme mentionné précédemment, c’est la logique d’affaire et non la version de notre SQL Server. L’emphase doit être mis sur l’isolation de notre logique d’affaire. Elle doit être pure.

Pour ce faire, différentes approches ont été documentées dans le passé. Hexagonal Architecture (Port and Adapter), Onion Architecture et Clean Architecture sont toutes des variantes, mais partent du même principe. On doit inverser les dépendances. Pour ma part, je trouve que l’appellation « architecture en oignon » est celle qui parle le plus. On veut ajouter des couches autour de notre domaine. On représente nos couches de la manière suivante :

OnionArch

http://jeffreypalermo.com/blog/the-onion-architecture-part-1/

Le sens des dépendances doit toujours être vers le centre. Une fois qu’on a intégré ce paradigme dans notre code (merci aux IoC Containers), on obtient une architecture testable et plus modulaire. Toutefois, reste encore à trouver une manière de regrouper ces différentes couches en composants.

Découpage en composants adapté aux microservices

Si on résume, le but est d’isoler les classes de domaine afin qu’elles soient indépendantes des détails d’implémentation comme les bases de données et les composants UI. Le réflexe que j’ai eu dans le passé a été de créer un composant séparé pour chaque élément d’infrastructure (UI, Données, Cross-cutting concern). En .NET, c’est le seul mécanisme qui permet d’éviter à une classe du domaine de pouvoir instancier une classe d’accès aux données directement. Le fait de faire une référence vers le domaine dans le composant d’accès aux données empêche donc d’avoir une référence dans l’autre sens, et donc, le domaine ne peut avoir de dépendance directe inverse, ce qui créerait une référence circulaire. Toutefois, cette approche n’est pas parfaite : elle ne permet pas d’hurler l’architecture. On peut toujours s’en tirer avec une nomenclature de composant, mais ce n’est pas ce qui a de plus évident. Par ailleurs, s’en sont suivi deux phénomènes intéressants :

  1. Les composants UI ne sont pas obligés de passer par le domaine pour accéder aux données, ce qui nuit à la centralisation de la logique d’affaire
    1. En effet, nous avons vite vu apparaître des classes de la couche UI qui accédaient directement aux données.
  2. Lors d’une modification au domaine, on devait effectuer des changements dans plus d’un composant, ce qui montre que la cohésion n’était peut-être pas au maximum.

Pour moi, le dernier chapitre du livre Clean Architecture de Uncle Bob est sans doute le plus révélateur. Écrit par Simon Brown (auteur du framework C4), ce chapitre décrit une manière de regrouper les classes différemment. Il propose de regrouper dans un même composant les éléments suivants :

  1. Couche service (pas confondre avec le projet Web API ou WCF qui sont plus du UI d’une certaine façon)
  2. Accès aux données

Dans ce composant, la seule chose qui demeure publique est la porte d’entrée du composant. On obtient une architecture comme celle qui suit :

MSFinal

(En pâle, les types qui sont internes)

Brown propose de tirer profit du compilateur pour faire respecter l’architecture en rendant internal les éléments d’accès aux données et les classes du domaine. On obtient une architecture plus parlante. Juste en regardant les composants on comprends où se trouve la gestion des commandes. Aussi, le découplage demeure inchangé. Par ailleurs, il devient facile de déplacer un concept d’un service vers un autre. On vient ainsi se donner de la flexibilité quand vient le temps de revoir le découpage des services en approche microservices. Pas d’analyse d’impact très longue, tout est dans le composant. C’est une façon d’adapter notre architecture en prévision d’une migration vers les microservices.

Les plus malins auront toutefois noté qu’il devient plus facile d’instancier une classe d’infrastructure et donc de ne pas respecter le sens des dépendances. C’est en effet un compromis, mais puisque ce problème ne peut pas se propager à l’extérieur du composant, on peut découvrir les fautes et le redressement sera en général plus rapide. On y gagne toutefois une plus forte cohésion et on évite de disperser la logique d’affaire.

Comme ces composants sont très autonomes, il devient facile de déployer ces derniers de différentes façons. L’évolution d’un composant comme celui-là peut être confié à une équipe indépendante étant donné qu’il a très peu de dépendances externes.

Conclusion

Même si l’adoption de l’approche microservice comporte de nombreux enjeux, il est possible de commencer dès aujourd’hui à se préparer dans notre code patrimonial. Une fois l’indépendance du code obtenu, il faut s’attaquer aux bases de données, ce qui n’est pas une mince affaire non plus. Comme la route est longue, ça nous force à travailler là où les gains sont les plus nécessaires. L’architecture microservice n’est pas une destination, mais plutôt un outil qu’on doit utiliser pour atteindre des objectifs spécifiques.