Les inscriptions sont désormais ouvertes pour Microservices mars 2023. Consultez l'ordre du jour et inscrivez-vous ici .
Rédacteur – Cette série d’articles en sept parties est désormais terminée :
Vous pouvez également télécharger l'ensemble complet des articles, ainsi que des informations sur la mise en œuvre de microservices à l'aide de NGINX Plus, sous forme d'ebook – Microservices : De la conception au déploiement .
Consultez également notre page Solutions de microservices .
Les microservices suscitent actuellement beaucoup d’attention : articles, blogs, discussions sur les réseaux sociaux et présentations lors de conférences. Ils se dirigent rapidement vers le pic des attentes gonflées du cycle Hype de Gartner . Dans le même temps, certains sceptiques au sein de la communauté des logiciels considèrent les microservices comme une nouveauté. Les sceptiques affirment que l’idée n’est qu’un changement de marque de SOA. Cependant, malgré le battage médiatique et le scepticisme, le modèle d’architecture de microservices présente des avantages significatifs, notamment lorsqu’il s’agit de permettre le développement et la livraison agiles d’applications d’entreprise complexes.
Cet article de blog est le premier d’une série en sept parties sur la conception, la création et le déploiement de microservices . Vous découvrirez l’approche et comment elle se compare au modèle d’architecture monolithique plus traditionnel. Cette série décrira les différents éléments d'une architecture de microservices. Vous découvrirez les avantages et les inconvénients du modèle d’architecture de microservices, s’il est judicieux pour votre projet et comment l’appliquer.
Voyons d’abord pourquoi vous devriez envisager d’utiliser des microservices.
Imaginons que vous commenciez à créer une toute nouvelle application de commande de taxi destinée à concurrencer Uber et Hailo. Après quelques réunions préliminaires et collecte des exigences, vous créerez un nouveau projet manuellement ou en utilisant un générateur fourni avec Rails, Spring Boot, Play ou Maven. Cette nouvelle application aurait une architecture hexagonale modulaire, comme dans le schéma suivant :
Au cœur de l’application se trouve la logique métier, qui est implémentée par des modules qui définissent des services, des objets de domaine et des événements. Autour du noyau se trouvent des adaptateurs qui s'interfacent avec le monde extérieur. Les exemples d’adaptateurs incluent les composants d’accès à la base de données, les composants de messagerie qui produisent et consomment des messages et les composants Web qui exposent des API ou implémentent une interface utilisateur.
Bien qu'ayant une architecture logiquement modulaire, l'application est packagée et déployée comme un monolithe. Le format réel dépend de la langue et du framework de l’application. Par exemple, de nombreuses applications Java sont empaquetées sous forme de fichiers WAR et déployées sur des serveurs d’applications tels que Tomcat ou Jetty. D’autres applications Java sont empaquetées sous forme de fichiers JAR exécutables autonomes. De même, les applications Rails et Node.js sont regroupées sous forme de hiérarchie de répertoires.
Les applications écrites dans ce style sont extrêmement courantes. Ils sont simples à développer puisque nos IDE et autres outils sont axés sur la création d'une application unique. Ces types d’applications sont également simples à tester. Vous pouvez implémenter des tests de bout en bout en lançant simplement l’application et en testant l’interface utilisateur avec Selenium. Les applications monolithiques sont également simples à déployer. Il vous suffit de copier l'application packagée sur un serveur. Vous pouvez également faire évoluer l’application en exécutant plusieurs copies derrière un équilibreur de charge. Au début du projet, cela fonctionne bien.
Malheureusement, cette approche simple présente une énorme limitation. Les applications réussies ont tendance à croître au fil du temps et à devenir finalement énormes. À chaque sprint, votre équipe de développement implémente quelques histoires supplémentaires, ce qui, bien sûr, signifie ajouter de nombreuses lignes de code. Après quelques années, votre petite application simple sera devenue un monolithe monstrueux . Pour donner un exemple extrême, j’ai récemment parlé à un développeur qui écrivait un outil pour analyser les dépendances entre les milliers de JAR dans son application de plusieurs millions de lignes de code (LOC). Je suis sûr qu’il a fallu l’effort concerté d’un grand nombre de développeurs pendant de nombreuses années pour créer une telle bête.
Une fois que votre application est devenue un monolithe volumineux et complexe, votre organisation de développement est probablement dans une situation difficile. Toute tentative de développement et de livraison agile échouera. L’un des problèmes majeurs est que l’application est extrêmement complexe. C'est tout simplement trop vaste pour qu'un seul développeur puisse le comprendre pleinement. Par conséquent, corriger les bugs et implémenter correctement de nouvelles fonctionnalités devient difficile et prend du temps. De plus, cela tend à devenir une spirale descendante. Si la base de code est difficile à comprendre, les modifications ne seront pas effectuées correctement. Vous vous retrouverez avec une boule de boue monstrueuse et incompréhensible.
La taille même de l’application ralentira également le développement. Plus l’application est grande, plus le temps de démarrage est long. Par exemple, dans une enquête récente, certains développeurs ont signalé des temps de démarrage pouvant aller jusqu’à 12 minutes. J’ai également entendu des anecdotes selon lesquelles des applications prenaient jusqu’à 40 minutes à démarrer. Si les développeurs doivent régulièrement redémarrer le serveur d’applications, une grande partie de leur journée sera consacrée à l’attente et leur productivité en souffrira.
Un autre problème avec une application monolithique volumineuse et complexe est qu’elle constitue un obstacle au déploiement continu. Aujourd’hui, l’état de l’art des applications SaaS consiste à pousser les modifications en production plusieurs fois par jour. Cela est extrêmement difficile à réaliser avec un monolithe complexe puisque vous devez redéployer l’intégralité de l’application pour pouvoir mettre à jour une partie de celle-ci. Les longs délais de démarrage que j’ai mentionnés plus tôt n’aideront pas non plus. De plus, comme l’impact d’un changement n’est généralement pas très bien compris, il est probable que vous deviez effectuer des tests manuels approfondis. Par conséquent, le déploiement continu est pratiquement impossible à réaliser.
Les applications monolithiques peuvent également être difficiles à mettre à l’échelle lorsque différents modules ont des exigences de ressources conflictuelles. Par exemple, un module peut implémenter une logique de traitement d’image gourmande en ressources CPU et serait idéalement déployé dans des instances Amazon EC2 Compute Optimized . Un autre module peut être une base de données en mémoire et mieux adapté aux instances optimisées en mémoire EC2 . Cependant, comme ces modules sont déployés ensemble, vous devez faire des compromis sur le choix du matériel.
Un autre problème avec les applications monolithiques est la fiabilité. Étant donné que tous les modules s’exécutent dans le même processus, un bogue dans n’importe quel module, comme une fuite de mémoire, peut potentiellement faire tomber l’ensemble du processus. De plus, comme toutes les instances de l’application sont identiques, ce bug aura un impact sur la disponibilité de l’ensemble de l’application.
Enfin et surtout, les applications monolithiques rendent extrêmement difficile l’adoption de nouveaux frameworks et langages. Par exemple, imaginons que vous ayez 2 millions de lignes de code écrites à l’aide du framework XYZ. Il serait extrêmement coûteux (en temps et en coût) de réécrire l’intégralité de l’application pour utiliser le nouveau framework ABC, même si ce dernier était considérablement meilleur. Il en résulte un énorme obstacle à l’adoption de nouvelles technologies. Vous êtes coincé avec les choix technologiques que vous avez faits au début du projet.
Pour résumer : vous avez une application critique pour l’entreprise qui a réussi et qui est devenue un monolithe monstrueux que très peu de développeurs, voire aucun, comprennent. Il est écrit à l’aide d’une technologie obsolète et improductive qui rend difficile l’embauche de développeurs talentueux. L'application est difficile à mettre à l'échelle et n'est pas fiable. Par conséquent, le développement et la livraison agiles d’applications sont impossibles.
Alors, que pouvez-vous faire à ce sujet ?
De nombreuses organisations, telles qu’Amazon, eBay et Netflix , ont résolu ce problème en adoptant ce que l’on appelle désormais le modèle d’architecture de microservices . Au lieu de construire une seule application monstrueuse et monolithique, l’idée est de diviser votre application en un ensemble de services plus petits et interconnectés.
Un service implémente généralement un ensemble de fonctionnalités distinctes, telles que la gestion des commandes, la gestion des clients, etc. Chaque microservice est une mini-application dotée de sa propre architecture hexagonale composée d’une logique métier ainsi que de divers adaptateurs. Certains microservices exposeraient une API qui est consommée par d’autres microservices ou par les clients de l’application. D'autres microservices peuvent implémenter une interface utilisateur Web. Au moment de l'exécution, chaque instance est souvent une machine virtuelle cloud ou un conteneur Docker.
Par exemple, une décomposition possible du système décrit précédemment est illustrée dans le diagramme suivant :
Chaque domaine fonctionnel de l'application est désormais implémenté par son propre microservice. De plus, l’application Web est divisée en un ensemble d’applications Web plus simples (comme une pour les passagers et une pour les chauffeurs dans notre exemple de commande de taxi). Cela facilite le déploiement d'expériences distinctes pour des utilisateurs, des appareils ou des cas d'utilisation spécialisés spécifiques.
Chaque service backend expose une API REST et la plupart des services consomment des API fournies par d'autres services. Par exemple, Driver Management utilise le serveur de notifications pour informer un conducteur disponible d'un trajet potentiel. Les services d'interface utilisateur appellent les autres services afin de restituer les pages Web. Les services peuvent également utiliser une communication asynchrone basée sur des messages. La communication interservices sera abordée plus en détail plus loin dans cette série.
Certaines API REST sont également exposées aux applications mobiles utilisées par les conducteurs et les passagers. Les applications n’ont cependant pas d’accès direct aux services backend. Au lieu de cela, la communication est médiatisée par un intermédiaire appelé passerelle API . La passerelle API est responsable de tâches telles que l'équilibrage de charge, la mise en cache, le contrôle d'accès, la mesure et la surveillance des API, et peut être mise en œuvre efficacement à l'aide de NGINX . Les articles ultérieurs de la série traiteront de la passerelle API .
Le modèle d'architecture de microservices correspond à la mise à l'échelle de l'axe Y du Scale Cube , qui est un modèle 3D d'évolutivité issu de l'excellent livre The Art of Scalability . Les deux autres axes de mise à l'échelle sont la mise à l'échelle de l'axe X, qui consiste à exécuter plusieurs copies identiques de l'application derrière un équilibreur de charge, et la mise à l'échelle de l'axe Z (ou partitionnement des données), où un attribut de la demande (par exemple, la clé primaire d'une ligne ou l'identité d'un client) est utilisé pour acheminer la demande vers un serveur particulier.
Les applications utilisent généralement les trois types de mise à l’échelle ensemble. La mise à l’échelle de l’axe Y décompose l’application en microservices comme indiqué ci-dessus dans la première figure de cette section. Lors de l’exécution, la mise à l’échelle de l’axe X exécute plusieurs instances de chaque service derrière un équilibreur de charge pour le débit et la disponibilité. Certaines applications peuvent également utiliser la mise à l’échelle de l’axe Z pour partitionner les services. Le diagramme suivant montre comment le service de gestion de voyage peut être déployé avec Docker exécuté sur Amazon EC2.
Lors de l’exécution, le service de gestion de voyage se compose de plusieurs instances de service. Chaque instance de service est un conteneur Docker. Pour assurer une haute disponibilité, les conteneurs s'exécutent sur plusieurs machines virtuelles Cloud. Devant les instances de service se trouve un équilibreur de charge tel que NGINX qui répartit les requêtes entre les instances. L'équilibreur de charge peut également gérer d'autres problèmes tels que la mise en cache , le contrôle d'accès , la mesure des API et la surveillance .
Le modèle d’architecture de microservices a un impact significatif sur la relation entre l’application et la base de données. Plutôt que de partager un schéma de base de données unique avec d’autres services, chaque service possède son propre schéma de base de données. D’un côté, cette approche est en contradiction avec l’idée d’un modèle de données à l’échelle de l’entreprise. En outre, cela entraîne souvent une duplication de certaines données. Cependant, disposer d'un schéma de base de données par service est essentiel si vous souhaitez bénéficier des microservices, car cela garantit un couplage faible. Le diagramme suivant montre l’architecture de la base de données pour l’exemple d’application.
Chacun des services possède sa propre base de données. De plus, un service peut utiliser un type de base de données le mieux adapté à ses besoins, ce que l’on appelle l’architecture de persistance polyglotte. Par exemple, Driver Management, qui trouve des conducteurs proches d’un passager potentiel, doit utiliser une base de données prenant en charge des requêtes géographiques efficaces.
En apparence, le modèle d’architecture de microservices est similaire à celui de SOA. Avec les deux approches, l’architecture se compose d’un ensemble de services. Cependant, une façon de considérer le modèle d'architecture de microservices est de le considérer comme une SOA sans la commercialisation et le bagage perçu des spécifications de services Web (WS‑*) et d'un bus de services d'entreprise (ESB). Les applications basées sur des microservices privilégient des protocoles plus simples et légers tels que REST, plutôt que WS‑*. Ils évitent également fortement d’utiliser les ESB et implémentent plutôt des fonctionnalités de type ESB dans les microservices eux-mêmes. Le modèle d’architecture de microservices rejette également d’autres parties de SOA, telles que le concept de schéma canonique.
Le modèle d’architecture de microservices présente un certain nombre d’avantages importants. Tout d’abord, il s’attaque au problème de la complexité. Il décompose ce qui serait autrement une application monolithique monstrueuse en un ensemble de services. Bien que la quantité totale de fonctionnalités reste inchangée, l'application a été divisée en blocs ou services gérables. Chaque service possède une limite bien définie sous la forme d'une API pilotée par RPC ou par messages. Le modèle d'architecture de microservices impose un niveau de modularité qui, dans la pratique, est extrêmement difficile à atteindre avec une base de code monolithique. Par conséquent, les services individuels sont beaucoup plus rapides à développer et beaucoup plus faciles à comprendre et à maintenir.
Deuxièmement, cette architecture permet à chaque service d’être développé indépendamment par une équipe concentrée sur ce service. Les développeurs sont libres de choisir les technologies qui leur conviennent, à condition que le service respecte le contrat API. Bien sûr, la plupart des organisations souhaiteraient éviter une anarchie complète et limiter les options technologiques. Cependant, cette liberté signifie que les développeurs ne sont plus obligés d’utiliser les technologies potentiellement obsolètes qui existaient au début d’un nouveau projet. Lors de la rédaction d’un nouveau service, ils ont la possibilité d’utiliser la technologie actuelle. De plus, comme les services sont relativement petits, il devient possible de réécrire un ancien service en utilisant la technologie actuelle.
Troisièmement, le modèle d’architecture de microservices permet à chaque microservice d’être déployé indépendamment. Les développeurs n’ont jamais besoin de coordonner le déploiement des modifications locales à leur service. Ces types de changements peuvent être déployés dès qu’ils ont été testés. L'équipe chargée de l'interface utilisateur peut, par exemple, effectuer des tests A/B et effectuer rapidement des itérations sur les modifications de l'interface utilisateur. Le modèle d’architecture de microservices permet un déploiement continu.
Enfin, le modèle d’architecture de microservices permet à chaque service d’être mis à l’échelle indépendamment. Vous pouvez déployer uniquement le nombre d’instances de chaque service qui répondent à ses contraintes de capacité et de disponibilité. De plus, vous pouvez utiliser le matériel qui correspond le mieux aux besoins en ressources d’un service. Par exemple, vous pouvez déployer un service de traitement d’image gourmand en CPU sur des instances EC2 Compute Optimized et déployer un service de base de données en mémoire sur des instances EC2 Memory-Optimized.
Comme l’écrivait Fred Brooks il y a près de 30 ans, il n’existe pas de solution miracle. Comme toute autre technologie, l’architecture des microservices présente des inconvénients. Un inconvénient est le nom lui-même. Le terme microservice accorde une importance excessive à la taille du service. En fait, certains développeurs préconisent la création de services LOC extrêmement précis de 10 à 100. Bien que les petits services soient préférables, il est important de se rappeler qu’ils constituent un moyen pour parvenir à une fin et non un objectif principal. L’objectif des microservices est de décomposer suffisamment l’application afin de faciliter le développement et le déploiement agiles des applications.
Un autre inconvénient majeur des microservices est la complexité qui découle du fait qu’une application de microservices est un système distribué. Les développeurs doivent choisir et mettre en œuvre un mécanisme de communication interprocessus basé sur la messagerie ou le RPC. De plus, ils doivent également écrire du code pour gérer les échecs partiels, car la destination d'une requête peut être lente ou indisponible. Bien que rien de tout cela ne soit sorcier, c’est beaucoup plus complexe que dans une application monolithique où les modules s’invoquent les uns les autres via des appels de méthodes/procédures au niveau du langage.
Un autre défi avec les microservices est l’architecture de base de données partitionnée. Les transactions commerciales qui mettent à jour plusieurs entités commerciales sont assez courantes. Ces types de transactions sont faciles à mettre en œuvre dans une application monolithique car il existe une seule base de données. Cependant, dans une application basée sur des microservices, vous devez mettre à jour plusieurs bases de données appartenant à différents services. L’utilisation de transactions distribuées n’est généralement pas une option, et pas seulement à cause du théorème CAP . Ils ne sont tout simplement pas pris en charge par la plupart des bases de données NoSQL et des courtiers de messagerie hautement évolutifs d'aujourd'hui. Vous finissez par devoir utiliser une approche basée sur la cohérence éventuelle, ce qui est plus difficile pour les développeurs.
Tester une application de microservices est également beaucoup plus complexe. Par exemple, avec un framework moderne comme Spring Boot, il est simple d'écrire une classe de test qui démarre une application Web monolithique et teste son API REST. En revanche, une classe de test similaire pour un service devrait lancer ce service et tous les services dont il dépend (ou au moins configurer des stubs pour ces services). Encore une fois, ce n’est pas sorcier, mais il est important de ne pas sous-estimer la complexité de cette tâche.
Un autre défi majeur du modèle d’architecture de microservices est la mise en œuvre de changements qui s’étendent sur plusieurs services. Par exemple, imaginons que vous implémentez une histoire qui nécessite des modifications des services A, B et C, où A dépend de B et B dépend de C. Dans une application monolithique, vous pourriez simplement modifier les modules correspondants, intégrer les modifications et les déployer en une seule fois. En revanche, dans un modèle d’architecture de microservices, vous devez planifier et coordonner soigneusement le déploiement des modifications sur chacun des services. Par exemple, vous devrez mettre à jour le service C, suivi du service B, puis enfin du service A. Heureusement, la plupart des modifications n’impactent généralement qu’un seul service et les modifications multiservices nécessitant une coordination sont relativement rares.
Le déploiement d’une application basée sur des microservices est également beaucoup plus complexe. Une application monolithique est simplement déployée sur un ensemble de serveurs identiques derrière un équilibreur de charge traditionnel. Chaque instance d'application est configurée avec les emplacements (hôte et ports) des services d'infrastructure tels que la base de données et un courtier de messages. En revanche, une application de microservices se compose généralement d’un grand nombre de services. Par exemple, Hailo propose 160 services différents et Netflix en propose plus de 600 selon Adrian Cockcroft [Editeur – Hailo a été acquis par MyTaxi.] . Chaque service aura plusieurs instances d’exécution. Il s’agit de beaucoup plus de pièces mobiles qui doivent être configurées, déployées, mises à l’échelle et surveillées. De plus, vous devrez également implémenter un mécanisme de découverte de service (discuté dans un article ultérieur) qui permet à un service de découvrir les emplacements (hôtes et ports) de tous les autres services avec lesquels il doit communiquer. Les approches traditionnelles basées sur des tickets d’incident et manuelles ne peuvent pas s’adapter à ce niveau de complexité. Par conséquent, le déploiement réussi d’une application de microservices nécessite un meilleur contrôle des méthodes de déploiement par les développeurs et un niveau élevé d’automatisation.
Une approche de l’automatisation consiste à utiliser un PaaS prêt à l’emploi tel que Cloud Foundry . Un PaaS offre aux développeurs un moyen simple de déployer et de gérer leurs microservices. Cela les isole des préoccupations telles que l’approvisionnement et la configuration des ressources informatiques. Dans le même temps, les professionnels des systèmes et des réseaux qui configurent le PaaS peuvent garantir la conformité aux meilleures pratiques et aux politiques de l’entreprise. Une autre façon d’automatiser le déploiement de microservices est de développer ce qui est essentiellement votre propre PaaS. Un point de départ typique consiste à utiliser une solution de clustering, telle que Kubernetes , en conjonction avec une technologie telle que Docker. Plus loin dans cette série, nous verrons comment les approches de distribution d’applications basées sur des logiciels comme NGINX Plus, qui gère facilement la mise en cache, le contrôle d’accès, la mesure des API et la surveillance au niveau du microservice, peuvent aider à résoudre ce problème.
Créer des applications complexes est par nature difficile. Une architecture monolithique n'a de sens que pour les applications simples et légères. Vous vous retrouverez dans un monde de souffrance si vous l’utilisez pour des applications complexes. Le modèle d’architecture Microservices est le meilleur choix pour les applications complexes et évolutives malgré les inconvénients et les défis de mise en œuvre.
Dans les prochains articles de blog, je me plongerai dans les détails de divers aspects du modèle d’architecture de microservices et discuterai de sujets tels que la découverte de services, les options de déploiement de services et les stratégies de refactorisation d’une application monolithique en services.
Restez à l'écoute…
Rédacteur – Cette série d’articles en sept parties est désormais terminée :
Vous pouvez également télécharger l'ensemble complet des articles, ainsi que des informations sur la mise en œuvre de microservices à l'aide de NGINX Plus, sous forme d'ebook – Microservices : De la conception au déploiement .
Le blogueur invité Chris Richardson est le fondateur du CloudFoundry.com original, un PaaS (Platform as a Service) Java précoce pour Amazon EC2. Il conseille désormais les organisations pour améliorer la manière dont elles développent et déploient des applications.
« Cet article de blog peut faire référence à des produits qui ne sont plus disponibles et/ou qui ne sont plus pris en charge. Pour obtenir les informations les plus récentes sur les produits et solutions F5 NGINX disponibles, explorez notre famille de produits NGINX . NGINX fait désormais partie de F5. Tous les liens NGINX.com précédents redirigeront vers un contenu NGINX similaire sur F5.com."