Libérer du monolithe

Et comment nous avons évolué avec Microservices

(19 juillet 2018)

Un singe de code dirigeant Monolith

Chez Conio, tout a commencé avec ce que lon appelle communément un «monolithe». Cest-à-dire une base de code unique contenant tout de lapplication complète: ses composants frontaux, ses composants backend, ses services API, ses tâches darrière-plan; enfer, même les scripts devops. Et cela a très bien fonctionné au début. Seuls quelques ingénieurs logiciels, travaillant sur des zones distinctes de la base de code (donc très peu de risques de conflits de changement de code), faciles à déployer. Concentrez-vous sur lécriture des fonctionnalités de lapplication, sans vous soucier de grand chose dautre Comment avons-nous abordé le déploiement? Avec seulement quelques clients bêta conscients des progrès constants, ce nétait pas un réel problème darrêter les services pendant un certain temps, de déployer la base de code complète (peu importe la taille des changements globaux et sils incluaient des migrations de bases de données), et ensuite relancer les services.

Cétait vraiment satisfaisant de voir un produit prendre forme à partir de zéro et de recevoir les appréciations des clients finaux. Cependant, nous savions très bien que cette approche nétait pas adaptée à une entreprise Fintech moderne.

Et alors?

Avec la plupart des applications logicielles, les clients sont assez tolérants. Oui, Whatsapp peut cesser de fonctionner et avoir une panne de quelques heures: certainement une nuisance, mais pas un problème perçu. Il en va de même pour Pokemon Go ou votre application de jeu préférée. Cependant, ce nest pas le cas lorsquil sagit dargent : lhumeur change si vous ne pouvez pas vous connecter à votre compte bancaire ou si vous ne pouvez pas le faire faire des opérations commerciales. Cest encore pire dans le cas des applications de crypto-monnaie: la plupart des gens se souviennent des infâmes erreurs du passé, et chaque fois quils ne sont pas en mesure daccéder à leurs fonds de crypto-monnaie, même pour une courte période de temps, des spéculations surviennent. Cest juste. Cest votre argent, et vous devriez avoir peu ou pas de problème lorsque vous voulez lutiliser.

Le Monolith ci-dessus nest pas adapté à un tel scénario: toute modification de la base de code en production nécessiterait un déploiement complet, avec les temps darrêt associés. Chaque jour, nous travaillons à améliorer nos services en corrigeant des bugs, en rendant notre interface encore plus conviviale, en supprimant les anciennes fonctionnalités et en en ajoutant de nouvelles qui ont une meilleure utilisation. Nous publions souvent ces mises à jour quotidiennement afin que nos clients puissent en profiter immédiatement, et nous nous efforçons de ne pas avoir dimpact sur lexpérience client. Autrement dit, quelle que soit la formule que nous concoctons dans les coulisses, doit être invisible pour le monde extérieur (du moins, la plupart du temps). Nous nous sommes donc éloignés du Monolith et avons choisi ce que lon appelle communément «larchitecture des microservices».

Evolution via les microservices

Lénorme base de code unique étroitement collée est maintenant décomposée en parties plus petites, chacune représentant un service particulier. À chaque exécution, les services communiquent entre eux de manière synchrone via le protocole HTTP standard et de manière asynchrone via des files dattente (gérées par exemple par RabbitMQ et Apache Kafka).

Interactions dans une architecture de microservices

Il est assez difficile de commencer à diviser le monolithe en composants plus petits, mais cela en vaut vraiment la peine leffort. En termes militaires, cest très similaire à ce que Jules César a fait pour gouverner de manière constante le vaste territoire de Gallia: « divisez pour conquérir ».

1) Le produit peut être déployé en continu. Une mise à jour de code sapplique désormais uniquement à un microservice: dans la plupart des cas, elle peut être immédiatement déployée en production et publiée sans impact pour le client

2) Le code est plus facile à gérer. Du point de vue de lorganisation de lentreprise, les choses changent lorsquune équipe de 2 ingénieurs logiciels devient une équipe de 10 ingénieurs logiciels. Cest plus efficace et avec moins de conflits de code lorsque chaque membre de léquipe est responsable de son propre microservice.

3) Le code est plus facile à maintenir. Une architecture Microservices nécessite, par nature, la définition dune interface pour communiquer avec le monde externe (que ce soit lapplication frontale ou un autre service backend) et elle est complètement isolée de tout autre point de vue. Cela permet de revoir, re-concevoir ou même réécrire complètement à partir de zéro (même dans différentes langues si cela est pratique) des composants uniques de lapplication sans impacter le reste.

4) Les performances peuvent être améliorées. Chaque microservice peut désormais utiliser sa langue la plus appropriée. Les composants de calcul cryptographique lourds peuvent par exemple être optimisés en C, tandis que les services API en Python et les tâches de longue durée en Go.

5) Amélioration de lisolation et de la sécurité du code. Chaque microservice peut être exécuté dans son propre conteneur Docker, offrant ainsi une isolation des privilèges, une séparation du réseau et des données et, dune importance primordiale pour une phase de croissance, un énorme potentiel dévolutivité.

Les microservices sont-ils alors la réponse?

Bien sûr, il ny a pas de repas gratuit. Une architecture de microservices comporte également ses propres défis difficiles:

1) Complexité opérationnelle. Les ingénieurs DevOps sont absolument nécessaires pour adoucir les subtilités du nouveau processus de déploiement.

2) Le gonflement matériel. Les microservices sont souvent exécutés dans des conteneurs Docker; dès que le nombre de microservices prolifère, il devient de plus en plus difficile dexécuter lapplication complète sur le même matériel quauparavant.

3) Surcommunication dintercommunication: chaque requête peut avoir besoin dinteragir avec un ou plusieurs différents microservices via le réseau. Cela peut entraîner une latence accrue et peut être sujet à des pannes temporaires. Afin de mettre en œuvre des services résilients et daméliorer lévolutivité de lensemble du système, il est nécessaire de déplacer les interactions vers la messagerie asynchrone (par exemple en utilisant Apache Kafka et / ou RabbitMQ)

4) Cohérence à terme. Cest probablement le défi le plus difficile dune architecture de microservices. Étant donné un seul microservice, il est possible de créer des transactions SGBDR dans ses limites . Malheureusement, un problème courant dans les architectures distribuées est de traiter plusieurs transactions qui ne sont pas dans les mêmes limites. En conséquence, le système peut se retrouver dans un état illégal et irrécupérable. Afin datténuer ces problèmes, Conio adopte différentes stratégies:

  1. En suivant les pratiques de Domain Driven Design, décomposez les domaines de niveau supérieur en sous-domaines et confinez-les dans des contextes délimités individuellement ; chaque contexte borné est implémenté en tant que microservice, où les limites de transaction sont appliquées. Cela résout la possibilité davoir des incohérences pour des sous-domaines spécifiques.
  2. Implémentez des interactions asynchrones idempotentes, qui résolvent tôt ou tard les incohérences.
  3. Dans la mesure du possible, évitez toute action qui pourrait impliquer plusieurs sous-domaines.

5) Rapports complexes. Étant donné que chaque sous-domaine vit dans un contexte limité spécifique, des rapports complexes qui impliquent plusieurs sous-domaines peuvent nécessiter dinterroger des données à partir de plusieurs sources de données: cela peut avoir un impact négatif à la fois sur lexpressivité des domaines et sur lévolutivité du système. Ici, dans Conio , nous avons adopté une architecture CQRS pour prendre en charge lactivité du backoffice et les rapports danalyse commerciale.

6 ) Système de journalisation. Chaque élément dun système distribué contribue à la création du journal de lensemble du système. Cependant, il est nécessaire de mettre en œuvre des outils capables de créer les connexions nécessaires entre tous ces journaux séparés afin davoir un journal unifié pour chaque interaction. Ici, dans Conio, nous utilisons la pile ELK (ElasticSearch, Logstash, Kibana) pour stocker et interroger les données du journal: chaque journal est enrichi avec les identifiants de corrélation nécessaires qui permettent le journal unifié mentionné ci-dessus.

Narrêtez jamais lévolution

Notre avis? La décomposition de la base de code unique initiale doit être considérée comme une tâche à long terme , avec des améliorations constantes. Chez Conio, il nous a fallu quelques années où, étape par étape, nous sommes passés dune base de code massive à plus de 100 microservices . Nous sommes arrivés à un point où nous sommes fiers des résultats, mais en même temps nous continuons à explorer. Plusieurs nouvelles optimisations sont possibles: passer de Docker Swarm à Kubernetes? Migration des services backend-for-frontend vers des fonctions lambda sans serveur? Vous passez à un flux dopérations de déploiement continu complet? Les possibilités sont infinies.

Nous avons abordé ici un certain nombre de sujets et de technologies. Dans les prochains articles, nous partagerons plus de détails sur nos résultats et nos progrès. Si vous le souhaitez, nhésitez pas à commenter et à nous raconter votre expérience.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *