Comment nous avons cassé l'ancienne cabane et construit un gratte-ciel à sa place

Zurab Bely, chef d'équipe, pratique Java, raconte son histoire de travailler dans un projet pour une grande entreprise et partage son expérience.

Comment je me suis installé ...


Je me suis lancé dans le projet à la fin de l'été 2017 en tant que développeur ordinaire. Je ne peux pas dire qu’à cette époque je l’aimais beaucoup: les technologies utilisées dans le projet étaient anciennes, la communication au sein de l’équipe était minimisée, la communication avec le client était difficile et improductive. Le projet m'a donc rencontré. A cette époque, je n'avais qu'une envie: m'en sortir rapidement.

Je vais vous parler un peu du projet dans son ensemble. Il s'agit du portail officiel d'une grande entreprise avec des informations générales, des nouvelles, des promotions et d'autres contenus. Toutes les newsletters marketing contiennent des liens vers certaines pages du site, c'est-à-dire que la charge est stable dans la moyenne, mais à certains moments, elle peut atteindre des valeurs élevées. La stabilité et l'accessibilité de l'application Web nécessitent une attention particulière - chaque minute d'arrêt de service entraîne des pertes importantes pour le client.

Bidonville qui louchait dans le vent


Au début, j'ai principalement étudié l'état technique du projet, corrigé des bugs mineurs et apporté des améliorations mineures. D'un point de vue technique, l'application avait l'air terrible: une architecture monolithique construite autour d'une version commerciale obsolète de dotCMS, code écrit en Java 6ème version, lors du neuvième rendu côté serveur de la partie client sur le framework Velocity, qui n'était pas encore arrivé il y a plusieurs années a été pris en charge. Chaque instance a été lancée dans JBoss AS et routée à l'aide de Nginx. Les fuites de mémoire ont entraîné un redémarrage constant des nœuds et le manque de mise en cache normale a entraîné une augmentation de la charge du serveur. Mais le plus gros éclat était les modifications apportées au code CMS. Ils ont exclu la possibilité d'une mise à niveau indolore vers une version plus récente. Une bonne illustration de cela était la transition de la version 3.2 à 3.7, qui se terminait à ce moment-là. La transition vers la prochaine version mineure a pris plus d'un an. Aucune solution populaire, comme Spring Framework, React.js, l'architecture de microservice, Docker, etc., n'était hors de question. En approfondissant le projet, les conséquences d'une telle condition technique sont devenues visibles. Le plus grave d'entre eux était l'incapacité d'exécuter l'application localement pour le développement et le débogage. Toute l'équipe de 8 personnes a travaillé sur un stand de développement, où une copie de la version de production de l'application a été déployée. En conséquence, un seul développeur a pu déboguer son code en même temps et le roulement du code mis à jour a bloqué toute l'équipe. L'apogée a été une vente ratée, au cours de laquelle des millions de lettres, SMS et notifications push ont été envoyés à différents utilisateurs via des dizaines de canaux - des dizaines de milliers de sessions ont été ouvertes en même temps. Les serveurs ne pouvaient pas le supporter, et la plupart du temps le portail n'était pas disponible. L'application n'évolue pas bien. Il n'y avait qu'une seule façon de procéder: déployer une autre copie côte à côte et équilibrer les charges entre elles à l'aide de Nginx. Et chaque livraison du code de production impliquait beaucoup de travail manuel et prenait plusieurs heures.

Six mois après mon implication dans le projet, alors que la situation commençait déjà à devenir incontrôlable, il a été décidé de changer radicalement la situation. Le processus de transition a commencé. Les changements ont touché tous les domaines: composition de l'équipe, processus de travail, architecture et composante technique de l'application.

Nous avons construit, construit ...


Tout d'abord, des changements de personnel ont eu lieu. Remplacés par plusieurs développeurs, ils ont fait de moi un chef d'équipe. La transition vers des solutions modernes a commencé avec l'implication de personnes dans l'équipe qui avaient une expérience de travail avec elles.

Les changements de procédure étaient plus globaux. D'ici là, le développement était déjà en cours sur la méthodologie Agile- + Scrum, sprints de deux semaines avec livraison à la fin de chaque itération. Mais en fait, cela n'a pas seulement augmenté la vitesse de travail, mais au contraire ralenti. Les rassemblements quotidiens ont duré une heure et demie à deux heures et n'ont donné aucun résultat. La planification et le toilettage se sont transformés en différends, en jurant ou en communication simple. Il y avait quelque chose à voir avec ça. Au départ, il était très difficile de changer quoi que ce soit dans ce sens - au nom du client, l'équipe a presque perdu confiance, surtout après une vente infructueuse. Chaque changement devait être justifié, discuté et prouvé pendant longtemps. Curieusement, mais l'initiative est venue du client. De leur côté, un scrum-master a été impliqué pour contrôler la bonne application des approches et méthodologies, déboguer les processus et mettre l'équipe au travail. Bien qu'il n'ait été attiré que par quelques sprints, cela nous a aidés à bien assembler la fondation. L'approche de la communication avec le client a beaucoup changé. Nous avons commencé à discuter plus souvent des problèmes dans les processus, les rétrospectives ont commencé à avoir lieu de manière plus productive, les développeurs étaient plus disposés à donner leur avis et le client, pour sa part, est allé de l'avant et a soutenu le processus de transition de toutes les manières.

Mais, honnêtement, je dirai honnêtement: il y a eu pas mal de moments où certains changements au sein de l'équipe ont été effectués «à l'aveugle», et après l'apparition de résultats positifs, cela a été signalé au client. Depuis six mois, l'attitude a changé pour une communication de travail confortable. Cela a été suivi de plusieurs teambuildings, de réunions d'une journée et de deux jours de toute l'équipe de développement avec l'équipe du client (responsable marketing, analyste, designer, responsable produit, responsables de contenu, etc.), visites conjointes au bar. Après un an, et à ce jour, la communication peut être qualifiée de conviviale. L'atmosphère est devenue conviviale, détendue et confortable. Bien sûr, cela ne se passe pas sans conflits, mais même dans la famille la plus heureuse, il y a parfois des querelles.

Au cours de cette période, des changements non moins intéressants se sont produits dans le code d'application, dans son architecture et dans les solutions utilisées. Si vous n'êtes pas techniquement averti, vous pouvez sauter en toute sécurité le texte entier jusqu'à la conclusion. Et si vous avez de la chance comme moi, bienvenue! La transition entière peut être divisée en plusieurs étapes. À propos de chacun plus en détail.

Étape 1. Identification des zones à problèmes critiques.

Tout était aussi simple et clair que possible. Tout d'abord, il fallait se débarrasser de la dépendance d'un produit commercial tiers, couper un monolithe et permettre un débogage local. Je voulais séparer le code client et code serveur, le distribuer architecturalement et physiquement. Un autre problème est la qualification. Le projet manquait complètement de tests automatiques. Cela a rendu la transition un peu difficile, car tout devait être vérifié manuellement. Étant donné qu'il n'y a jamais eu de tâches techniques pour le fonctionnel (les spécificités du projet sont affectées ici), il y avait une forte probabilité de manquer quelque chose. Après avoir peint les zones problématiques, nous avons de nouveau examiné la liste. Cela ressemblait à un plan. Il est temps de construire un gratte-ciel!

Étape 2. Mise à jour de la base de code.

L'étape la plus longue. Tout a commencé avec la transition vers une architecture de service (à ne pas confondre avec les microservices). L'idée était la suivante: diviser l'application en plusieurs services distincts, chacun traitant de sa tâche spécifique. Les services n'étaient pas censés être «micro», mais je ne voulais pas non plus tout mettre dans une seule chaudière. Chaque service était censé être une application Spring Boot écrite en Java SE 8 et exécutée sur Tomcat.

Le premier était le soi-disant. "Content service", qui est devenu une couche entre la future application et le CMS. C'est devenu une abstraction sur le chemin du contenu. Il a été supposé que toutes les demandes que nous avions précédemment faites directement dans le CMS seront effectuées via ce service, mais en utilisant déjà le protocole HTTP. Une telle solution nous a permis de réduire la connectivité et a créé la possibilité de remplacer par la suite dotCMS par un analogue plus moderne ou même d'éliminer l'utilisation de produits commerciaux et d'écrire notre propre solution adaptée à nos tâches (pour l'avenir, je dirai que c'est notre chemin).

Immédiatement créé le terrain pour la séparation du code avant et arrière. Ils ont créé un service frontal, qui est devenu responsable de la distribution du code écrit sur le React. Nous avons vissé npm, configuré le nœud et débogué l'assemblage - tout est comme il se doit selon les tendances modernes de la partie client.

En général, la fonctionnalité a été allouée au service selon l'algorithme suivant:

  • créé une nouvelle application Spring Boot avec toutes les dépendances et paramètres nécessaires;
  • porté toute la logique de base (souvent écrit à partir de zéro, en se référant à l'ancien code, uniquement pour s'assurer que vous n'avez oublié aucune nuance), par exemple, pour le service de mise en cache, ce sont les options à ajouter au cache, à lire à partir de celui-ci et à le désactiver;
  • toutes les nouvelles fonctionnalités ont toujours été écrites en utilisant le nouveau service;
  • réécriture progressive d'anciennes parties de l'application dans un nouveau service par ordre d'importance.

Au début, nous en avions quelques-uns:

  • Service de contenu. Agi comme une couche entre l'application et le CMS.
  • Service de cache. Référentiel simple sur Spring Cache.
  • Service AA. Au début, il n'était engagé que dans la diffusion d'informations sur un utilisateur autorisé. Le reste est resté à l'intérieur de dotCMS.
  • Service avant. Responsable de la distribution du code client.

Étape 3. Autotests.

Compte tenu de toute l'expérience du projet, nous avons décidé que la présence de tests fonctionnels simplifie grandement la vie et la possible mise à jour ultérieure de l'application. Il est temps de les introduire dans le projet. Les tests unitaires du code, malheureusement pour le dire, ont calé presque immédiatement. Ils ont pris beaucoup de temps à écrire et à prendre en charge, et nous en avions très peu, car, en plus de réécrire le code, des tâches en cours sur de nouvelles fonctions nous pendaient, et des bugs faisaient souvent surface. Il a été décidé de se concentrer uniquement sur le test de l'interface de l'application à l'aide de Selenium. Cela, d'une part, nous a permis d'effectuer plus facilement des tests de régression avant de livrer en production, d'autre part, il est devenu possible de refactoriser côté serveur, en surveillant l'état côté client. L’équipe ne disposait pas d’un automate, et la rédaction et le maintien de la pertinence des tests automatiques nécessitent des coûts supplémentaires. Ils n'ont pas recyclé l'un des testeurs et une autre personne a été ajoutée à l'équipe.

Étape 4. Automatisation du déploiement.

Maintenant que nous avons des services séparés, lorsque le frontend s'est séparé du backend, lorsque la fonctionnalité principale a commencé à être couverte par des auto-tests, il est temps d'ouvrir une canette de bière et d'automatiser tout le travail manuel de déploiement et de support de l'application localement, sur des serveurs de démonstration et de production. La découpe du monolithe en morceaux et l'utilisation de Spring Boot nous ont ouvert de nouveaux horizons.

Les développeurs ont pu déboguer le code localement, en n'exécutant que la partie des fonctionnalités nécessaires pour cela. Les bancs d'essai ont finalement commencé à être utilisés conformément à leur destination - il y avait déjà du code plus ou moins débogué, prêt pour les tests initiaux et de qualification. Mais il y avait encore beaucoup de travaux faits à la main qui ont gaspillé notre précieux temps et notre énergie. Après avoir étudié le problème et trié les solutions, nous nous sommes installés sur un tas de Gradle + TeamCity. Étant donné que le projet a été construit par Gradle, ajouter quelque chose de nouveau n'avait pas de sens et que les scripts qui ont été écrits se sont avérés indépendants de la plate-forme, ils peuvent être exécutés sur n'importe quel système d'exploitation, à distance ou localement. Et cela a permis non seulement d'utiliser n'importe quelle solution pour CI / CD, mais aussi de changer sans problème la plate-forme en une autre. TeamCity a été choisi en raison de sa riche fonctionnalité intégrée, de la présence d'une grande communauté et d'une longue liste de plug-ins pour toutes les occasions, ainsi que de son intégration avec l'environnement de développement Intellij IDEA.

À l'heure actuelle, il existe plus de 100 scripts d'optimisation et plus de 300 tâches dans le système CI pour les exécuter avec différents paramètres. Ce n'est pas seulement un déploiement pour tester des bancs et la livraison à la production, mais aussi travailler avec des journaux, la gestion des serveurs, le routage et juste des solutions pour les tâches de routine du même type. Certaines tâches ont été supprimées de nos épaules. Les gestionnaires de contenu ont pu vider le cache eux-mêmes. Les gars du support technique ont eu l'opportunité de tirer des services individuels par eux-mêmes, pour effectuer des actions de réanimation primaires. Le sommeil des développeurs est devenu plus profond et plus calme.

Étape 5. Propre CMS.

Une fois qu'il a été possible d'abstraire du CMS commercial, il est devenu possible de recevoir des données d'une autre source, sans réécrire le code de l'application. Où obtenir telle ou telle donnée a été décidée par le service de contenu. Après avoir recherché des solutions toutes faites et les avoir analysées, nous avons décidé d'écrire notre propre système de gestion de contenu, car aucune d'entre elles ne répondait pleinement à nos besoins. Écrire votre propre CMS est un processus sans fin, de nouveaux besoins et souhaits apparaissent constamment. Nous avons sélectionné quelques fonctionnalités de base et sommes allés dans le beau monde du développement. Pour lancer la première version en prod, nous avions un mois et demi d'homme. Comme la fonctionnalité est prête dans le nouveau CMS, nous transférons le contenu de l'ancien vers lui. Toutes les nouvelles pages n'ont plus rien à voir avec dotCMS. Au fil du temps, cela a permis d'abandonner la version payante et de passer à la version communautaire, et à l'avenir d'abandonner complètement quelque chose de tiers.

Étape 6. Relecture.

Après avoir retroussé nos pantalons, nous avons commencé notre voyage dans le monde de la programmation hipster. Cette étape de mon projet était la dernière du processus de restructuration. Cela continue jusqu'à ce jour. Le domaine principal pour lequel cette étape est généralement apparue est la mise à l'échelle. Le bundle Docker + Kubernetes + Consul vous permet non seulement de faciliter le déploiement sur différents serveurs et de gérer chaque service séparément, mais également de faire évoluer de manière flexible l'ensemble de l'application, ne le faites que dans les endroits où cela est nécessaire, et aussi longtemps que cela est nécessaire. Plus en détail, je ne peux peindre que lorsque nous passerons entièrement à cette solution en production.

... et finalement construit. Hourra!



Un an s'est écoulé depuis le début de la mise à jour de l'application. Ce sont maintenant 26 services REST écrits dans Spring Boot. Chacun a une documentation API détaillée dans l'interface utilisateur de Swagger. Le côté client est écrit dans React.js et séparé du côté serveur. Toutes les pages principales du portail sont couvertes de tests. Réalisation de plusieurs ventes importantes. La transition vers les technologies modernes, l'élimination du vieux code et le fonctionnement stable des serveurs motivent fortement les développeurs. De «comme nous l'avons dit, nous le faisons», le projet est entré dans le courant dominant, où tout le monde est intéressé par le succès, offre ses propres options d'amélioration et d'optimisation. L'attitude du client envers l'équipe a changé, une ambiance conviviale s'est créée.

C'est le résultat du travail de toute l'équipe, de chaque développeur et testeur, des managers et des clients. C'était très dur, nerveux et parfois au bord d'une faute. Mais la cohésion d'équipe, la planification compétente et la connaissance des résultats ont permis de surmonter toutes les difficultés.

Source: https://habr.com/ru/post/fr457624/


All Articles