L'architecture de microservices est depuis longtemps la norme de facto dans le développement de systèmes grands et complexes. Il présente un certain nombre d'avantages: c'est une division stricte en modules, une faible connectivité, une résistance aux pannes, une sortie progressive en production et une version indépendante des composants.
Il est vrai, souvent, en parlant d'architecture de microservices, seule l'architecture de backend est mentionnée, et le frontend tel qu'il était, reste monolithique. Il s'avère que nous avons fait un excellent soutien, et l'avant nous tire en arrière.
Aujourd'hui, je vais vous dire comment nous avons fait le front du microservice dans notre solution SaaS et quels problèmes nous avons rencontrés.
Problème
Au départ, le développement dans notre entreprise ressemblait à ceci: il y a de nombreuses équipes impliquées dans le développement de microservices, chacune d'elles publiant sa propre API. Et il y a une équipe distincte qui développe SPA pour l'utilisateur final, en utilisant l'API de différents microservices. Avec cette approche, tout fonctionne: les développeurs de microservices savent tout sur leur implémentation, et les développeurs SPA connaissent toutes les subtilités des interactions des utilisateurs. Mais il y avait un problème: maintenant chaque front-end devrait connaître toutes les subtilités de tous les microservices. Il y a de plus en plus de microservices, de plus en plus de fournisseurs frontaux - et Agile commence à s'effondrer, à mesure que la spécialisation au sein de l'équipe apparaît, c'est-à-dire que l'interchangeabilité et l'universalité disparaissent.
Nous sommes donc passés à l'étape suivante - le développement modulaire. L'équipe frontend était divisée en sous-commandes. Chacun était responsable de sa partie de la demande. C'est devenu beaucoup mieux, mais au fil du temps, cette approche s'est épuisée pour plusieurs raisons.
- Tous les modules sont hétérogènes, avec leurs propres spécificités. Pour chaque module, sa propre technologie est mieux adaptée. Dans le même temps, le choix de la technologie est une tâche difficile dans les conditions du SPA.
- Depuis l'application SPA (et dans le monde moderne, cela signifie la compilation en un seul paquet ou au moins un assemblage), seule l'application entière peut être émise en même temps. Le risque de chaque extradition augmente.
- Il devient plus difficile de gérer la dépendance. Différents modules nécessitent des versions de dépendance différentes (éventuellement spécifiques). Quelqu'un n'est pas prêt à passer à l'API de dépendance mise à jour, et quelqu'un ne peut pas créer de fonctionnalité en raison d'un bogue dans l'ancienne branche de dépendance.
- En raison du deuxième point, le cycle de libération de tous les modules doit être synchronisé. Tout le monde attend les retardataires.
Frontend de coupe
Il y a eu un moment d'accumulation de masse critique, et ils ont décidé de diviser le frontal en ... microservices frontaux. Définissons ce qu'est un microservice frontal:
- une partie complètement isolée de l'interface utilisateur, en aucun cas dépendante des autres; isolement radical; littéralement développé comme une application autonome;
- chaque microservice frontal est responsable d'un certain ensemble de fonctions commerciales du début à la fin, c'est-à-dire qu'il est entièrement fonctionnel en soi;
- peut être écrit sur n'importe quelle technologie.
Mais nous sommes allés plus loin et avons introduit un autre niveau de division.
Concept de fragment
Nous appelons un fragment un bundle composé de
js + css +
. En fait, il s'agit d'une partie indépendante de l'interface utilisateur, qui doit se conformer à un ensemble de règles de développement pour pouvoir être utilisée dans un SPA général. Par exemple, tous les styles doivent être aussi spécifiques que possible pour le fragment. Aucune tentative ne doit être faite pour interagir directement avec d'autres fragments. Vous devez avoir une méthode spéciale à laquelle vous pouvez passer l'élément DOM où le fragment doit être dessiné.
Grâce au descripteur, nous pouvons enregistrer des informations sur tous les fragments enregistrés de l'environnement, puis y avoir accès par ID.
Cette approche vous permet de placer deux applications écrites sur des cadres différents sur une seule page. Il permet également d'écrire du code universel qui vous permettra de charger dynamiquement les fragments nécessaires sur la page, de les initialiser et de gérer le cycle de vie. Pour la plupart des cadres modernes, il suffit de suivre les «règles d'hygiène» pour que cela soit possible.
Dans les cas où le fragment n'a pas la possibilité de «cohabiter» avec d'autres sur la même page, il existe un script de secours dans lequel nous dessinons le fragment dans un iframe (la solution aux problèmes associés dépasse le cadre de cet article).
Tout ce qu'un développeur qui souhaite utiliser un extrait de code existant sur une page doit faire est:
- Connectez le script de plate-forme de microservice à la page.
<script src="//{URL to static cache service}/api/v1/mui-platform/muiPlatform.js"></script>
- Appelez la méthode d'ajout d'un fragment à la page.
window.MUI.createFragment(
De plus, pour la communication entre les fragments, il existe un bus construit sur
Observable
et
rxjs
. Il est écrit sur NativeJS. De plus, le SDK est livré avec des wrappers pour différents frameworks qui aident à utiliser ce bus en mode natif. Un exemple pour Angular 6 est une méthode utilitaire qui renvoie
rxjs/Observable
:
import {fromEvent} from "@netcracker/mui-platform/angular2-factory/modules/shared/utils/event-utils" fromEvent("<event-name>"); fromEvent(EventClassType);
De plus, la plate-forme fournit un ensemble de services qui sont souvent utilisés par différents fragments et sont basiques dans notre infrastructure. Ce sont des services tels que la localisation / internationalisation, le service d'autorisation, le travail avec les cookies inter-domaines, le stockage local et bien plus encore. Pour leur utilisation, le SDK fournit également des wrappers pour divers cadres.
Combiner le frontend
Par exemple, nous pouvons considérer cette approche dans la zone d'administration SPA (elle combine différents paramètres possibles de différents microservices). Nous pouvons créer le contenu de chaque signet sous forme de fragment distinct, qui sera fourni et développé séparément par chaque microservice. Grâce à cela, nous pouvons créer un simple «en-tête» qui montrera le microservice correspondant en cliquant sur un signet.

Nous développons l'idée d'un fragment
Le développement d'un signet avec un fragment ne permet pas toujours de résoudre tous les problèmes possibles. Il est souvent nécessaire de développer une certaine partie de l'interface utilisateur dans un microservice, qui sera ensuite réutilisée dans un autre microservice.
Et ici, des fragments nous aident aussi! Puisque tout le fragment a besoin d'un élément DOM pour le rendu, nous donnons à tout microservice une API globale à travers laquelle il peut placer n'importe quel fragment à l'intérieur de son arborescence DOM. Pour ce faire, passez simplement l'ID de fragment et le conteneur dans lequel il doit être dessiné. Le reste se fera tout seul!
Nous pouvons maintenant construire une «poupée gigogne» de n'importe quel niveau d'imbrication et réutiliser des pièces entières d'interface utilisateur sans avoir besoin de soutien à plusieurs endroits.
Il arrive souvent que sur une page il y ait plusieurs fragments qui devraient changer leur état lors du changement de certaines données communes sur la page. Pour ce faire, ils disposent d'un bus d'événements global (NativeJS) à travers lequel ils peuvent communiquer et répondre aux changements.

Services partagés
Dans une architecture de microservices, des services centraux apparaissent inévitablement, des données dont tout le monde a besoin. Par exemple, un service de localisation qui stocke les traductions. Si chaque microservice commence individuellement à monter pour ces données sur le serveur, nous obtenons juste un arbre de requêtes lors de l'initialisation.
Pour résoudre ce problème, nous avons développé des implémentations de services NativeJS qui permettent d'accéder à ces données. Cela a permis de ne pas faire de requêtes et de données de cache inutiles. Dans certains cas, même de sortie de ces données sur une page en HTML à l'avance pour se débarrasser complètement des demandes.
De plus, des wrappers ont été développés sur nos services pour différents frameworks afin de rendre leur utilisation très naturelle (DI, interface fixe).
Avantages des microservices frontaux
La chose la plus importante que nous obtenons de la division d'un monolithe en fragments est la possibilité de sélectionner des technologies pour chaque équipe individuellement et une gestion transparente des dépendances. Mais aussi, cela donne:
- domaines de responsabilité très clairement divisés;
- émission indépendante: chaque fragment peut avoir son propre cycle de libération;
- augmenter la stabilité de la solution dans son ensemble, car l'émission de fragments individuels n'affecte pas les autres;
- la possibilité de restaurer facilement des fonctionnalités, de les déployer partiellement auprès d'un public;
- le fragment est facilement placé dans la tête de chaque développeur, ce qui conduit à une réelle
interchangeabilité des membres de l'équipe; en outre, chaque frontal peut mieux comprendre toutes les subtilités de l'interaction avec le back-end correspondant.
Une solution avec un frontal microseris semble bonne. En effet, maintenant chaque fragment (microservice) peut décider lui-même comment déployer: si vous avez juste besoin de nginx pour distribuer la statique, un middleware à part entière pour agréger les demandes de sauvegarde ou de prise en charge des websockets, ou une autre spécificité sous la forme d'un protocole de transfert de données binaires dans http. De plus, les fragments peuvent choisir leurs propres méthodes d'assemblage, méthodes d'optimisation, etc.
Inconvénients des microservices frontaux
Vous ne pouvez jamais vous passer d'une mouche dans la pommade.
- L'interaction entre les fragments ne peut pas être assurée par des méthodes de tubes standard (DI, par exemple).
- Que faire des dépendances partagées? Après tout, la taille de l'application augmentera à pas de géant, si elles ne sont pas retirées des fragments.
- Quoi qu'il en soit, un seul devrait être responsable de l'acheminement dans l'application finale.
- Que faire si l'un des fragments est inaccessible / ne peut pas être dessiné.
- On ne sait pas quoi faire du fait que différents microservices peuvent se trouver sur différents domaines.
Conclusion
Notre expérience avec cette approche a prouvé sa viabilité. La vitesse de sortie des fonctions en production a considérablement augmenté. Le nombre de dépendances implicites entre les parties de l'interface a été réduit à presque zéro. Nous avons obtenu une interface utilisateur cohérente. Vous pouvez tester des fonctionnalités en toute sécurité sans impliquer un grand nombre de personnes.
Malheureusement, dans un article, il est très difficile de couvrir toute la gamme des problèmes et des solutions qui peuvent être trouvés sur la façon de répéter une telle architecture. Mais pour nous, les avantages l'emportent clairement sur les inconvénients. Si Habr s'intéresse à révéler les détails de la mise en œuvre de cette approche, nous écrirons une suite!