5 questions à se poser pour bien comprendre les micro frontends

le 01/09/2021 par Pierre Isabel
Tags: Software Engineering

Le concept de micro frontends fait son apparition en novembre 2016 dans le radar ThoughtWorks, listant les usages techs émergents et les classant par intérêt estimé. Depuis avril 2019, les micro frontends résident dans la plus haute catégorie d’intérêt, suggérant aux entreprises d'adopter cette technique. Aujourd’hui très en vogue, les micro frontends sont utilisés par de grands noms du web tels que Spotify, Zalando, AirBnB ou encore Leroy Merlin en France.

Cet article introduit des notions d’architectures web front-end. Pour être au point sur le sujet, voici une suite d’articles OCTO qui résument bien les patterns classiques utilisés :

À la découverte des architectures WEB front-end (1/4) Les sites WEB statiques. | OCTO Talks !

A travers 5 questions principales, l’objectif de cet article est de partager les problématiques autour du concept de micro frontends et de donner des perspectives concrètes d’application.

1ère problématique : de quoi parle-t-on ?

Construire des micro frontends, c’est repenser un site web comme un ensemble de fonctionnalités indépendantes, développées de bout en bout par des équipes indépendantes, depuis la gestion de base de données jusqu’à l’interface utilisateur.

Autrement dit, l'architecture micro frontend c’est faire d’une application web front-end un ensemble d’applications indépendantes (cycle de vie, complexité, technologie) cohabitant et communiquant sur le même site.

L’objectif des micro frontends est dans la continuité de celui des microservices : fragmenter la complexité pour avoir des codes isolés, spécifiques, maintenables et déployables en continu les uns indépendamment des autres : diviser pour mieux régner.

2e problématique : concrètement, pourquoi faire ?

On remarque aujourd’hui que certains grands acteurs ont perdu la maintenabilité de leur site web, et ce pour des raisons diverses : les technologies utilisées sont désuètes, les harnais de tests ne sont pas suffisants pour sécuriser l’application, une modification sur une fonctionnalité entraîne une régression sur d’autres fonctionnalités… En bref, chaque évolution est douloureuse.

Le fait est que les exigences business ainsi que les attentes en termes d’expérience utilisateur et de fonctionnalités évoluent, quant à elles, constamment. On pourrait se dire que dans les cas les plus extrêmes, la solution la plus simple serait de repartir de zéro. Malheureusement la réalité d’une entreprise est plus complexe que cela, et peu sont prêtes à prendre le risque de recréer une application de A à Z, reprenant des fonctionnalités développées depuis plusieurs années, sans garantie réelle que le résultat soit plus optimal.

C’est là où les micro frontends sont attendus : pouvoir progressivement faire évoluer l’application, brique par brique, en continuant de générer du trafic : au final pouvoir continuer à être agile.

Si l’idée est séduisante, elle introduit tout de même plusieurs subtilités à prendre en compte. Dans la suite de l’article, nous nous focaliserons sur l’aspect technique que représente la mise en place de micro frontends.

3e problématique : front + front = front ?

Aujourd’hui quand on parle de front-end, on pense à une interface utilisateur unique construite en Vue, React, Angular, Ember ou toute autre combinaison de HTML/CSS/JS. Pourtant, lorsque l’on intègre du contenu à son site via la balise HTML iframe (une vidéo YouTube par exemple), c’est déjà un premier pas vers le côté obscur, euh… le concept de micro frontend.

Malheureusement, l’iframe a peu évolué depuis son apparition, ce qui rend son utilisation peu recommandée pour des questions de responsive design, de sécurité applicative et de référencement naturel. De plus, les possibilités de communications sont très restreintes puisqu’elles se limitent à window.postMessage(), qui on va le voir, n’est pas la méthode la plus ergonomique...

Pas de panique ! Les frameworks front-end ont pour énorme avantage d’avoir un socle commun : JavaScript et les APIs du DOM.

JS pour tous, tous pour JS !

Il existe donc de nombreuses approches pour faire des micro frontends. Comme un modeste article ne suffirait pas à toutes les lister, nous nous focaliserons sur trois techniques très différentes montrant les perspectives d’architecture.

1. Web Components

Les Web Components sont comme leur nom l’indique des composants web. A l’inverse des composants React, Vue, Angular ou de tout autre framework JavaScript, ces composants sont supportés nativement par la majorité des navigateurs. Concrètement un web component est composé d’un template HTML encapsulé dans un Shadow DOM - document imbriqué dans le document et “caché” du reste de l’arbre - auquel sont attachés les scripts et les styles du web component. Pour injecter un web component dans son application, il suffit alors d’une balise personnalisée dans le code HTML et d’un script utilisant l’API Custom Elements qui permet de définir et d’injecter notre micro frontend dans cette balise.

<html>
 <head>
  <script src="https://mes-micro-frontends.fr/web-components.js" async></script>
 </head>
 <body>
   ...
    <react-web-component></react-web-component>
    <lit-element-web-component></lit-element-web-component>
    <autre-web-component></autre-web-component>
   ...
 </body>
</html>

Aujourd’hui supportés par la grande majorité des navigateurs, il est très simple de créer des Web Components avec du JavaScript natif mais aussi via les technos web actuelles - cf. la librairie @vue/web-component-wrapper pour Vue.js, la librairie @angular/elements pour Angular, ou encore le framework Direflow pour React.js - pratique si on veut conserver certains mécanismes de développement !

👍 Les plus
  • Les web components sont réutilisables et adaptables à presque tous les cas d’usage,
  • On peut les encapsuler dans un Shadow DOM qui protège notamment de tout conflits de style éventuel,
  • On peut imbriquer des web components dans des web components #frontendception.
👎 Les moins
  • Les web components sont générés côté client par le script JS associé. De ce fait, pour les rendre côté serveur en cas de besoin en référencement naturel par exemple, c’est une autre paire de manches !
  • L’encapsulation via Shadow DOM pose aujourd’hui des problèmes d’accessibilité web encore non résolus.
⏩ En bref

Les web components sont très utiles pour l’encapsulation de services réutilisables sur plusieurs sites. On pourrait imaginer une infinité d'exemples d’utilisation, de l’affichage de publicités personnalisées à l’intégration de formulaires d’adhésion à des assurances sur des sites d’e-commerce.

2. Node.js / Express / EJS

Aujourd’hui, de plus en plus d’applications utilisent le rendu côté serveur (Server-Side Rendering, SSR) pour coupler l’affichage dynamique des applications rendues côté client et le référencement naturel qu’offrent les MPAs. On les appelle des applications universelles. Aujourd’hui les frameworks les plus connus sont Next.js (React) et Nuxt.js (Vue). Grâce au module de templating EJS d’Express, on peut alors composer des applications rendues côté serveur sur la même page.

Pour ce faire, chaque application vivant à une adresse différente est requêtée, assignée à une variable EJS, puis rendue en fonction des templates.

/* index.js */
server.get("/", (req, res) =>
 Promise.all([
   get("https://adresse-react.fr"),
   get("https://adresse-vue.fr"),
   get("https://adresse-angular.fr"),
   get("https://adresse-ember.fr")
 ])
   .then((réponses) => {
     res.render("index", {
       react: réponses[0],
       vue: réponses[1],
       angular: réponses[2],
       ember: réponses[3],
     });
   })
   .catch((erreur) => res.send(erreur.message))
)
<html>
 <head>...</head>
 <body>
    <%- react %>
    <%- vue %>
    <%- angular %>
    <%- ember %>
 </body>
</html>

Illustration d’implémentation via EJS

👍 Les plus
  • Le référencement naturel est optimisé car le premier affichage du site et de ses micro frontends dans le navigateur ne nécessite pas l’exécution de JavaScript, plus long à charger que HTML et CSS, et non lu par la majorité des web crawlers.
👎 Les moins
  • On retrouve le problème classique des MPAs : le rafraîchissement automatique lors d’un changement de page est un frein à la fluidité de la navigation, et donc à l’expérience utilisateur. Dommage quand on fait des applications SSR.
⏩ En bref

L’utilisation d’un module de templating rendu côté serveur comme EJS avec Express pourrait être intéressante quand le besoin de SEO est un critère essentiel. Cependant aujourd’hui le rafraîchissement des pages restreint la fluidité de l’expérience utilisateur, c’est pourquoi on tend à ne plus développer d’applications MPA. Cette méthode ne semble donc pas pérenne pour l’expérience utilisateur. On peut néanmoins imaginer qu’il s’agisse d’une bonne méthode pour migrer progressivement une MPA complexe vers une application rendue côté serveur.

3. Framework Single-SPA

Single-SPA est une technique à part puisqu’il s’agit d’un framework dédié à l’implémentation de micro frontends à partir de plusieurs SPA (d’où son nom). On appelle cela un "méta-framework", catégorie émergente avec le développement des micro frontends, visant le besoin de centraliser la gestion des cycles de vie et la communication inter-applications.

Le principe de Single-SPA est le suivant : chaque micro frontend implémente 3 méthodes - bootstrap, mount et unmount - permettant respectivement de créer l’application, de l’attacher au DOM et de l’en retirer. Une application “racine” (root-config) permet d’orchestrer le positionnement et l’affichage dynamique des applications, en fonction des routes.

👍 Les plus
  • L’avantage principal de Single-SPA est de pouvoir migrer une application SPA existante en micro frontend grâce aux librairies qu’il propose
👎 Les moins
  • La documentation ne permet pas encore une prise en main facile,
  • On observe encore des bugs d’affichage non résolus dans certains cas (l’unmount des micro frontends Angular est encore douteux),
  • Comme le framework est basé sur des SPA, le rendu côté serveur n’est a priori pas disponible.
⏩ En bref

Ce framework encore très récent n’est pas aujourd’hui 100% fiable, bien qu’il présente beaucoup de potentiel. Se pose aussi la question de la dépendance aux librairies que le framework propose, même si ces dernières ont au moins pour avantage de ne pas apporter trop de configuration spécifique dans les applications. Il serait donc intéressant d’observer son évolution à l’avenir.

4e problématique : Comment les applications communiquent entre elles ?

Des micro frontends qui n’interagissent pas n’auraient pas vraiment d’intérêt à cohabiter sur le même site. Mais alors, comment faire alors qu’ils sont potentiellement de technos différentes ? On pourrait se limiter aux paramètres de l’URL, mais il existe de nombreuses façons de passer des informations d’une application à l’autre, et encore une fois JavaScript et les APIs du DOM nous aident grandement dans cette tâche.

1. Attributes et properties

Dans le cas particulier des Web components, on peut passer des données avec les attributes directement via le code HTML...

<product-details product-id="1234567890"></product-details>

Par exemple, une fiche produit ne pourrait avoir besoin que de l’index du produit souhaité...

... Mais aussi avec ses properties via le code JavaScript !

const monWebComponent = document.getElementById("mon-web-component")
 
monWebComponent.produit = {
   id: "1234567890",
   nom: "Mon produit",
   prix: 30.5
}

...Ou bien on pourrait lui passer tout un objet produit !

👍 Les plus
  • L’approche attributes est semblable au transfert de données de composant parent à composant enfant dans les frameworks SPA actuels. C’est donc une façon de faire qui semble intuitive pour les développeurs front,
  • Dans le cas de l’approche properties, certaines librairies permettent même de simplifier l’utilisation de ces dernières en passant les données - même complexes - comme des attributes ! (Un peu comme les props de React par exemple)
👎 Les moins
  • Le format de données passé en attributes reste assez limité, car n’acceptant que des données sous forme de chaînes de caractères,
  • Ces données sont directement visibles dans le code, attention donc aux données sensibles !
⏩ En bref

Ces deux approches spécifiques aux web components peuvent déjà suffire à réaliser des micro frontends coordonnés avec le contexte de l’application parente, et sont simples à implémenter. Avantage supplémentaire pour les web components vis-à-vis des autres implémentations !

2. DOM / Custom Events

Le DOM est notamment constitué d’une interface orchestrant les events, événements qui ont lieu au sein de la page qu’ils soient provoqués par l’utilisateur (clic de souris, touche enfoncée par exemple), ou par d’autres éléments (vidéo/audio en pause par exemple).

On peut créer et utiliser ces events pour faire passer des données via l’élément window. On distingue pour ce faire deux principes : window.postMessage() et les Custom Events.

/* émetteur.js */
const message = {
 type: "important",
 texte: "Ce message est important",
};
// DOM Event
window.postMessage(message, "https://url-destinataire.fr");
// Custom Event
const événement = new CustomEvent("message.important", {
 detail: message
});
window.dispatchEvent(événement);
/* récepteur.js */
window.addEventListener("message.important", traitementDuMessage);
 
function traitementDuMessage(event) {
   // la donnée est accessible dans event.data ou event.detail
}

Illustration d’implémentation émetteur /  récepteur des données via les événements du DOM

Il sera préférable de privilégier les Custom Events à window.postMessage() dans tous les cas d’usage à l’exception des iframes qui ne permettent que l’utilisation de cette dernière méthode à cause de l’encapsulation particulière qu’ils induisent. En effet la méthode window.postMessage() pouvant être appelée depuis et vers n’importe quel domaine, il faut être particulièrement vigilant à la réception dans un souci de sécurité applicative.

👍 Les plus
  • Leur utilisation est simple,
  • Elle permet d’assurer la réactivité des données entre les micro frontends d’une même page.
👎 Les moins
  • Leur utilisation crée des couplages directs entre les applications émettrices et réceptrices, couplages que l’on cherche justement à éviter,
  • La gestion des différents events peut devenir rapidement complexe en fonction de leur nombre.
⏩ En bref

Les  events sont la base de la communication des micro frontends : elles sont suffisamment évoluées pour permettre la transmission de données riches et complexes et sont utilisables à n’importe quel niveau de l’application.

Le problème principal de cette méthode reste les couplages qu’elle crée entre les micro frontends, ce qui demande une rigueur en termes de contrats d’interface entre les applications.

Enfin deux micro frontends qui communiquent avec les events sont des applications qui doivent être présentes simultanément dans le DOM. Sinon ce serait un peu comme faire un album et placer le microphone dans le placard à balais à côté du studio d’enregistrement : pas très utile.

Donc si par exemple sur un site d’e-commerce on souhaite ajouter un produit au panier et que le micro frontend “fiche produit” est sur une page différente du micro frontend “panier”, alors on ne peut pas utiliser cette méthode.

3. Solution de state management (Redux / Vuex)

L’approche composant apportée par les frameworks web actuels comme React ou Vue entraîne notamment des dépendances parent-enfants pour le passage de données. Afin de  s’abstraire de cette complexité, les solutions dites de state management Redux (pour React) et Vuex (pour Vue) permettent à tout composant d’avoir le même niveau d’accès et d’écriture aux données partagées de l’application, rendues indépendantes des composants.

Ce concept, on peut l’appliquer sur plusieurs micro frontends : pour ce faire, une solution est d’attacher nos méthodes de lecture (selectors/getters) et de modification (actions) au window (encore et toujours).

👍 Les plus
  • La donnée partagée est disponible à tout endroit de l’application, et indépendante de tout micro frontend,
  • Elle persiste indépendamment des cycles de vie des micro frontends,
  • Utiliser Vuex ou Redux permet de conserver la mise à jour des données de façon réactive.
👎 Les moins
  • Cette solution (via window) reste néanmoins dangereuse : n’importe qui peut avoir accès à cet élément via la console du navigateur et donc dans ce cas avoir accès à toutes les données communes en lecture et en écriture, ce qui n’est pas toujours souhaitable.
⏩ En bref

Le state management est un concept très intéressant dans le contexte des micro frontends. Aujourd’hui les solutions actuelles - principalement Redux et Vuex - restent très dépendantes de leur framework ce qui limite les libertés que sont censés apporter les micro frontends. Il ne serait donc pas impossible de voir émerger des librairies qui permettraient de s’affranchir de ces frameworks et d’apporter une sécurité à la lecture et l’écriture des données.

4. sessionStorage / localStorage / cookies

Ces 3 dernières méthodes de communication sont similaires car elles utilisent le stockage de données non pas dans la page, mais via le navigateur. La différence étant que le sessionStorage est la donnée stockée et accessible uniquement dans un onglet, le localStorage est la donnée accessible par tout onglet du navigateur qui expire à la fermeture du navigateur, et les cookies, données stockées et accessibles par tout onglet du navigateur qui expirent après une durée renseignée.

👍 Les plus
  • Tout comme le state management, l’avantage principal de ces méthodes est de pouvoir avoir accès en lecture et en écriture à des données globales en tout point de l’application, l’avantage supplémentaire étant d’être totalement indépendant des changements de pages.
👎 Les moins
  • On retrouve les mêmes inconvénients que précédemment : ces données sont accessibles et modifiables par l’utilisateur directement dans la console, attention donc à la sécurité applicative,
  • Les objets stockés ne sont qu’au format string,
  • Ces données sont “rigides” : une modification n'entraînera pas de mise à jour dynamique dans les applications, mais seulement au rafraîchissement ou au changement de page,
  • Le stockage du localStorage par application est limité par les navigateurs, ainsi que celui des cookies.
⏩ En bref

Ces données ont pour avantage principal par rapport au state management de ne nécessiter aucune librairie pour fonctionner étant donné qu’elles sont stockées nativement par les navigateurs. Ces données constituent le canal idéal de notre communication inter-micro frontends : 100% indépendante, découplée et persistante. Il faudra cependant seulement se limiter à des données non sensibles et au format string (bye bye les fonctions utilitaires partagées donc…)

Comme on peut le voir, la communication inter-application est l’enjeu principal des micro frontends car elle détermine toute la complexité de l’application globale.

En effet, deux micro frontends qui communiquent doivent partager un modèle de donnée, un contrat d’interface, sans quoi ils ne peuvent se comprendre. Et si ce modèle change, il doit être modifié dans les deux micro frontends : on crée forcément un couplage.

Trop de connexions entre les applications et on perd l’intérêt du découpage, tout en ajoutant de la complexité par rapport à un simple monolithe. Il faut donc avoir une vraie réflexion sur le découpage des micro frontends et leurs connexions pour éviter cet anti-pattern.

5e problématique : Quelle stratégie de tests utiliser ?

Construire des micro frontends, c’est aussi créer de la complexité technique. Or qui dit complexité technique, dit aussi nécessité de mettre en place un solide harnais de tests automatisés, sans quoi notre superbe architecture risque de tourner au Titanic (sans Leonardo DiCaprio ni Céline Dion pour que l’histoire soit belle).

Pour rappel les tests automatisés se décomposent principalement en tests unitaires, d’intégration, et de bout en bout (cf. l'article OCTO : La pyramide des tests par la pratique). Les tests unitaires vérifient le comportement d’une portion de code totalement ou partiellement isolée de ses dépendances, et les tests d’intégration vérifient que plusieurs composants fonctionnent bien ensemble. Chaque micro frontend étant une application indépendante, il doit avoir ses propres tests unitaires et d’intégration.

En plus de ces tests, on pourra ajouter des tests de contrats, tests à mi-chemin entre unitaires et d’intégration, indépendants des micro frontends, et qui vont s’assurer que la communication inter-applications est opérationnelle en vérifiant que les interfaces sont bien respectées à l’émission et la réception des données. Ainsi, s' il y a un changement cassant, on sait quel micro frontend est responsable et on est sûrs que les modifications ne seront pas ajoutées tant qu’il y aura une désynchronisation.

Enfin les tests de bout en bout (ou tests end-to-end) servent quant à eux à vérifier le bon fonctionnement d’un parcours utilisateur du début à la fin de sa navigation. Ce sont donc ces tests qui vont nous assurer que la cohabitation des micro frontends est cohérente et que l’application est utilisable ! Les tests de bout en bout étant les plus longs à exécuter, moins il y aura d’interactions entre les micro frontends, plus ces tests seront succincts, et donc rapides à exécuter. Or une modification sur un micro frontend entraîne une modification sur l’application globale : la CI de notre application doit exécuter les tests end-to-end à chaque fois, et on a encore une fois tout intérêt à rendre les micro frontends les plus indépendants possible.

N.B.: le framework Cypress de tests end-to-end a l’avantage de pouvoir passer outre l’encapsulation du Shadow DOM dans le cas où le choix technique serait les web components.

(6e problématique) : Ai-je vraiment besoin des micro frontends ?

Après avoir fait le tour des étapes pour construire des micro frontends, on se rend compte qu’implémenter une telle architecture n’est pas sans conséquences : baisses potentielles de performances liées à des duplications de code, gestion supplémentaire de l’application globale avec la communication inter-applications, cohérence de l’expérience utilisateur, utilisation de CDN pour contrebalancer le temps de réponse des différentes applications…

Finalement le problème que résolvent vraiment les micro frontends, ce n’est pas un problème directement technique mais un problème organisationnel lié au passage à l’échelle : aucun des acteurs d’un projet impliquant un grand nombre d’entités ne peut maîtriser entièrement toutes les subtilités de celui-ci. En découpant un projet avec des équipes indépendantes, chaque îlot de travail peut alors se concentrer sur son périmètre et le maîtriser.

C’est pour cela qu’aujourd’hui les micro frontends sont réservés aux applications à forte complexité et que les grands acteurs du web s’y mettent pour faciliter leur implémentation : par exemple LitElement, librairie développée par Google, permet de créer des web components de plus en plus légers implémentables jusque dans les Progressive Web Apps, ou encore Webpack 5 et son Module Federation viennent faciliter la configuration des micro frontends au sein d’une application.

Même si c’est une architecture avec un beau potentiel, il ne vaut mieux pas se lancer corps et âme dans sa mise en place sans s’être posé quelques questions essentielles : ai-je une application suffisamment complexe pour que la mise en place de l’architecture soit gagnante ? Quelle solution technique serait la plus appropriée pour répondre à mon besoin ? Comment découper mon application intelligemment ?

Ce qu’il faut retenir

Si vous démarrez un projet et/ou que vous n’avez pas de réelle douleur pour faire évoluer votre application : YAGNI (You Aren’t Gonna Need It). Mieux vaut miser en premier lieu sur la qualité de son monolithe avant de le découper, on évite ainsi de créer une bombe à retardement, et dans tous les cas il existe mille et une façons de transiter son application en micro frontends en temps voulu !

Si d’autre part vous sentez que la complexité de votre application commence à vous faire perdre pied, alors les micro frontends sont peut-être la solution ! Faut-il encore savoir comment découper son site web… Une approche possible est de segmenter par logique métier (en s’inspirant du Domain Driven Design et de ses outils, cf. Domain Driven Design : des armes pour affronter la complexité). Ainsi chaque équipe peut se concentrer sur les besoins métier associés à son périmètre.

Pour vous convaincre, prenons l’exemple d’un site de réservation de véhicules :





Equipe
Besoin
Micro frontend associé
Spécificités métier
Logistique
Afficher les véhicules disponibles à la réservation chez les différents concessionnaires de la région
Carte interactive des véhicules disponibles
Carte interactive (librairie Leaflet, d3.js…)
Marketing
Afficher les offres sur les bons plans du moment
Recommandation, bannières publicitaires
Animations, liberté graphique
Ventes
Optimiser le taux de conversion
Tunnel d’achat, panier
Intégration systèmes de paiement (Paypal, Stripe, banques…)
Assurance
Offrir la possibilité de souscrire à une assurance dans le parcours utilisateur
Formulaire d’adhésion
Réglementations légales strictes

En segmentant clairement la proposition de valeur par logique métier, on se rend bien compte qu’un développeur ne pourra jamais dans un cas similaire tout maîtriser s’il doit toucher à tout. Donc si vous faites des micro frontends, ce sont sûrement des feature teams qu’il vous faut !

Pour aller plus loin

Plusieurs personnes à suivre et articles à lire si vous êtes intéressés par le sujet :