
Dans un mouvement qui pourrait redéfinir certaines approches du développement frontend, Dagger, une plateforme d'intégration et de déploiement continu (CI/CD), a décidé de se détacher des frameworks JavaScript traditionnels pour réinventer son interface utilisateur avec une technologie qui ne cesse de croître : Go et WebAssembly (WASM). Ce choix de remplacer React par Go couplé à WebAssembly a suscité un grand intérêt dans le domaine du développement, tant pour ses avantages que pour les défis qu’il soulève.
Dagger Cloud fournit une traçabilité unifiée et de bout en bout pour les builds et les tests - indépendamment des CI, des environnements de développement, des langages et des plates-formes. Tout ce que Dagger Engine peut exécuter, Dagger Cloud peut le tracer.
Quel en est l'intérêt ? Les développeurs expliquent : « "Qu'est-ce qui n'a pas fonctionné ? Pourquoi est-ce si lent ?" Si vous avez déjà perdu du temps à regarder les journaux de CI pour répondre à ces questions, il est temps d'adopter le traçage. Cela vous aidera à optimiser vos constructions et à résoudre plus rapidement les problèmes lorsque les choses tournent mal ».
Le contexte : un besoin de cohérence et de performance
« Il y a quelques semaines, nous avons lancé Dagger Cloud v3, une toute nouvelle interface utilisateur pour Dagger Cloud. L'une des principales différences entre la v3 et son prédécesseur v2 est que la nouvelle interface utilisateur est écrite en WebAssembly (WASM) à l'aide de Go. À première vue, ce choix peut sembler étrange (Go n'est généralement pas le premier langage auquel on pense lorsqu'on décide de programmer une interface utilisateur Web) mais nous avions de bonnes raisons ».
Avant de s'engager dans cette refonte, Dagger faisait face à une situation où deux interfaces utilisateurs distinctes étaient en parallèle :
- L'interface de terminal (TUI) développée en Go, directement intégrée au CLI de Dagger.
- L'interface Cloud développée en React pour le tableau de bord web, utilisé pour l’interaction avec les utilisateurs.
Maintenir deux interfaces différentes engendrait des défis majeurs. D’une part, la duplication du code ralentissait le développement. Chaque nouvelle fonctionnalité devait être réécrite et testée sur deux plateformes distinctes, ce qui non seulement alourdissait le processus de développement, mais aussi augmentait les risques d’incohérences entre les deux interfaces. D’autre part, la gestion des performances de l’interface Cloud devenait de plus en plus complexe. En particulier, la plateforme devait gérer de grandes quantités de données en temps réel, ce qui ralentissait l’expérience utilisateur et rendait l'interface moins réactive, surtout dans des scénarios de forte utilisation.
Deux bases de code = plus de travail, moins de fonctionnalités
Dagger fonctionne en construisant un DAG d'opérations et en les évaluant, souvent en parallèle. Par nature, il s'agit d'une chose difficile à afficher. Pour aider les utilisateurs à s'y retrouver, nous proposons deux interfaces de visualisation en temps réel : l'interface terminale de Dagger (TUI), incluse dans le CLI de Dagger, et Dagger Cloud, un tableau de bord Web en ligne. L'interface utilisateur de Dagger est implémentée en Go, et Dagger Cloud (pré-v3) a été écrit en React.
Il est évident que nous voulons que les deux interfaces utilisateur soient aussi proches que possible l'une de l'autre. Mais l'interprétation du flux d'événements de Dagger en temps réel et la production d'une interface utilisateur sont des tâches assez complexes. Certains des flux d'événements les plus complexes que nous ayons vus contiennent des centaines de milliers de données OpenTelemetry, et la gestion des structures de données qui les entourent devient très compliquée, très rapidement. L'interface Web ne pouvait souvent pas suivre l'énorme volume de données qu'elle devait traiter et elle devenait lente et laggy ; pour résoudre ce goulot d'étranglement des performances, nous avons été contraints d'adopter un modèle d'implémentation différent pour l'application React.
Nous nous sommes donc retrouvés avec deux interfaces essayant d'accomplir la même chose, l'une dans un langage et un écosystème (TypeScript/React), l'autre dans un langage et un écosystème totalement différents (Go), et nous ne pouvions pas facilement partager la logique métier entre elles. En tant que petite équipe, nous devons livrer rapidement. Avoir à réimplémenter chaque fonctionnalité deux fois était une taxe massive sur notre vélocité.
Nous avons commencé à réfléchir à une nouvelle approche de Dagger Cloud, avec deux objectifs principaux :
Dagger fonctionne en construisant un DAG d'opérations et en les évaluant, souvent en parallèle. Par nature, il s'agit d'une chose difficile à afficher. Pour aider les utilisateurs à s'y retrouver, nous proposons deux interfaces de visualisation en temps réel : l'interface terminale de Dagger (TUI), incluse dans le CLI de Dagger, et Dagger Cloud, un tableau de bord Web en ligne. L'interface utilisateur de Dagger est implémentée en Go, et Dagger Cloud (pré-v3) a été écrit en React.
Il est évident que nous voulons que les deux interfaces utilisateur soient aussi proches que possible l'une de l'autre. Mais l'interprétation du flux d'événements de Dagger en temps réel et la production d'une interface utilisateur sont des tâches assez complexes. Certains des flux d'événements les plus complexes que nous ayons vus contiennent des centaines de milliers de données OpenTelemetry, et la gestion des structures de données qui les entourent devient très compliquée, très rapidement. L'interface Web ne pouvait souvent pas suivre l'énorme volume de données qu'elle devait traiter et elle devenait lente et laggy ; pour résoudre ce goulot d'étranglement des performances, nous avons été contraints d'adopter un modèle d'implémentation différent pour l'application React.
Nous nous sommes donc retrouvés avec deux interfaces essayant d'accomplir la même chose, l'une dans un langage et un écosystème (TypeScript/React), l'autre dans un langage et un écosystème totalement différents (Go), et nous ne pouvions pas facilement partager la logique métier entre elles. En tant que petite équipe, nous devons livrer rapidement. Avoir à réimplémenter chaque fonctionnalité deux fois était une taxe massive sur notre vélocité.
Nous avons commencé à réfléchir à une nouvelle approche de Dagger Cloud, avec deux objectifs principaux :
- Unifier les bases de code, afin d'éliminer les doublons et de rendre plus efficace la livraison de nouvelles fonctionnalités.
- Tenir la promesse d'une interface Web claire et rapide, à la hauteur de la vitesse et de la performance de l'interface du terminal.
Go et WebAssembly : la solution choisie
Le choix de Go et WebAssembly ne s’est pas fait par hasard. Ce combo représente une approche relativement novatrice mais prometteuse pour le développement frontend, et ce pour plusieurs raisons.
Go : La Cohérence et la Productivité
Go, un langage de programmation compilé développé par Google, est particulièrement apprécié pour sa simplicité, sa rapidité et son efficacité. Son adoption au sein de Dagger était déjà bien ancrée pour le développement de leur interface en ligne de commande (CLI) et du backend. L'utilisation de Go pour l'interface utilisateur présente plusieurs avantages :
- Unité de code : En choisissant Go, l’équipe de développement a pu unifier l’écriture du code backend et frontend. Cela facilite non seulement la maintenance, mais aussi la collaboration entre les équipes de développement, qui sont toutes familières avec le même langage.
- Haute performance : Go étant un langage compilé, il offre des performances supérieures à celles des langages interprétés comme JavaScript. Ce facteur est essentiel pour des applications qui manipulent une grande quantité de données, comme c’est le cas de Dagger.
- Simplicité : Go est réputé pour sa syntaxe simple et son faible coût d’apprentissage, ce qui facilite la prise en main pour les développeurs, même ceux venant de langages de programmation plus complexes.
WebAssembly : L’exécution nativement dans le navigateur
WebAssembly (WASM) est une technologie qui permet d'exécuter du code à grande vitesse dans les navigateurs, presque à la vitesse native. L’une des caractéristiques principales de WASM est sa capacité à exécuter des langages autres que JavaScript dans l’environnement du navigateur, avec un rendu extrêmement rapide et efficace.
- Performance accrue : WASM permet à Dagger de déplacer des parties du code à exécuter directement dans le navigateur, au lieu de dépendre uniquement du JavaScript. Cela permet de maximiser les performances, surtout dans des cas d’utilisation avec de lourdes opérations de calcul.
- Portabilité : Le code Go compilé en WebAssembly peut être exécuté dans n’importe quel navigateur moderne, sans nécessiter de plugins supplémentaires. Cette portabilité est un atout majeur, car elle permet à Dagger de s’assurer que son application web fonctionne de manière fluide sur une large gamme de plateformes et de dispositifs.
- Réduction de la charge serveur : En déplaçant certaines tâches côté client grâce à WASM, Dagger a pu alléger la charge des serveurs, réduisant ainsi les coûts d'infrastructure et améliorant la réactivité de l’application.
Avec Dagger Cloud v3, nous voulions offrir une expérience plus fluide et plus rapide tout en maintenant une parité complète avec l'interface utilisateur du terminal Dagger, nous avons donc adopté WebAssembly (WASM).
L'adoption de WebAssembly (WASM) a été l'une des tendances croissantes dans l'écosystème du cloud au cours des dernières années. Et pour cause ! WASM vous permet d'exécuter du code écrit dans différents langages de programmation (comme Go, Rust, C++, etc.) dans des navigateurs web ou d'autres environnements à une vitesse quasi native.
En remplaçant notre ancienne application React par une interface utilisateur basée sur WebAssembly, nous avons unifié la logique commerciale sous-jacente entre le terminal Dagger et les expériences web. Auparavant, le maintien d'implémentations séparées en TypeScript/React (pour le web) et Go (pour le CLI) créait des doublons et ralentissait notre capacité à livrer des fonctionnalités de manière cohérente sur toutes les plateformes. Désormais, avec WASM, l'interface terminal et l'interface web partagent la même base de code backend.
Pour construire notre nouvelle interface utilisateur, nous avons utilisé Go-app, un framework Go pour les applications web progressives, qui a facilité la concrétisation de notre vision basée sur WebAssembly.
L'adoption de WebAssembly (WASM) a été l'une des tendances croissantes dans l'écosystème du cloud au cours des dernières années. Et pour cause ! WASM vous permet d'exécuter du code écrit dans différents langages de programmation (comme Go, Rust, C++, etc.) dans des navigateurs web ou d'autres environnements à une vitesse quasi native.
En remplaçant notre ancienne application React par une interface utilisateur basée sur WebAssembly, nous avons unifié la logique commerciale sous-jacente entre le terminal Dagger et les expériences web. Auparavant, le maintien d'implémentations séparées en TypeScript/React (pour le web) et Go (pour le CLI) créait des doublons et ralentissait notre capacité à livrer des fonctionnalités de manière cohérente sur toutes les plateformes. Désormais, avec WASM, l'interface terminal et l'interface web partagent la même base de code backend.
Pour construire notre nouvelle interface utilisateur, nous avons utilisé Go-app, un framework Go pour les applications web progressives, qui a facilité la concrétisation de notre vision basée sur WebAssembly.
Des erreurs plus faciles à lire
Les défis du passage à Go et WebAssembly
Bien que les avantages de cette approche soient évidents, la transition n’a pas été sans difficultés. Le passage de React à Go et WebAssembly a demandé un investissement important en termes de temps, de ressources et de compétences techniques.
- Maturité technologique de WASM : Le manque d'outils et de bibliothèques adaptées a obligé l’équipe de Dagger à développer eux-mêmes de nombreux composants frontend, ce qui a allongé le temps de développement.
- Limites de la mémoire WebAssembly : Le standard WebAssembly impose des limites de mémoire qui, bien qu’ayant été étendues dans les dernières versions des navigateurs, restent relativement faibles (environ 2 Go par application). Ce paramètre est particulièrement contraignant pour des applications qui doivent traiter de grandes quantités de données en temps réel, comme c’est le cas pour Dagger.
- Expérience utilisateur : Le passage de React, un framework optimisé pour le rendu d’interfaces riches et dynamiques, à une solution en Go et WebAssembly, a nécessité de repenser une partie de l’interactivité et de l’ergonomie de l’interface. Les composants web devaient être réinventés, ce qui a demandé une phase de prototypage et de tests exhaustive.
Notre objectif initial était de pouvoir réutiliser une base de code pour Dagger Cloud et l'interface utilisateur. Nous avons décidé assez tôt d'en faire une base de code Go. Techniquement, nous aurions pu faire l'inverse et utiliser TypeScript pour l'interface utilisateur. Mais nous sommes avant tout une équipe d'ingénieurs Go, et le choix de Go a permis aux autres membres de l'équipe de contribuer plus facilement, d'ajouter une fonctionnalité ou de passer quelques heures pour aider à déboguer un problème. En plus de la standardisation sur un seul langage, cela nous a donné de la flexibilité et a brisé les silos au sein de notre équipe.
Une fois que nous avons décidé d'exécuter le code Go directement dans le navigateur, WebAssembly était l'étape suivante logique. Mais il restait encore quelques défis à relever :
Une fois que nous avons décidé d'exécuter le code Go directement dans le navigateur, WebAssembly était l'étape suivante logique. Mais il restait encore quelques défis à relever :
- La combinaison Go + WebAssembly n'est pas encore aussi mature que React et d'autres frameworks JavaScript. Il n'existe pas de bibliothèques de composants prêts à l'emploi, les outils de développement ne sont pas aussi riches, etc. Nous savions que nous devrions créer la plupart de nos composants d'interface utilisateur à partir de zéro.
- Les applications WebAssembly sont soumises à une limite de mémoire de 2 Go dans la plupart des navigateurs. Nous nous attendions à ce que cela pose un problème lors de l'affichage de traces volumineuses, et nous savions que nous devrions faire beaucoup d'optimisation pour minimiser l'utilisation de la mémoire et maintenir l'interface utilisateur stable. Mais ce n'était pas entièrement mauvais ; le point positif était que toute amélioration de l'utilisation de la mémoire apportée à l'interface WebAssembly profiterait également aux utilisateurs de l'interface utilisateur, puisqu'il s'agissait maintenant d'une base de code partagée.
Des indicateurs d'état plus clairs
Les résultats : une interface plus performante et cohérente
Malgré ces défis, la refonte a été...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.