L'histoire de l'architecture Dodo IS : une première monolithique

Ou chaque entreprise malheureuse avec un monolithe est malheureuse à sa façon.

Le développement du système Dodo IS a commencé simultanément avec l'entreprise Dodo Pizza — en 2011. Au cœur se trouvait l'idée d'une numérisation complète et totale des processus commerciaux, par ses propres moyens, ce qui, déjà en 2011, soulevait de nombreuses questions et du scepticisme. Mais voilà, cela fait 9 ans que nous avançons sur ce chemin — avec un développement propre, qui a commencé par un monolithe.

Cet article est une « réponse » aux questions « Pourquoi réécrire l'architecture et apporter de tels changements massifs et longs ? » à l'article précédent « Histoire de l'architecture Dodo IS : le parcours du back-office ». Je vais commencer par expliquer comment le développement de Dodo IS a été lancé, à quoi ressemblait l'architecture initiale, comment de nouveaux modules apparaissaient et pour quelles raisons il a fallu effectuer des modifications majeures.

L'histoire de l'architecture Dodo IS : une première monolithique

La série d'articles « Qu'est-ce que Dodo IS ? » parlera de :

  1. Le monolithe précoce dans Dodo IS (2011-2015). (Vous êtes ici)

  2. Le parcours du back-office : des bases séparées et un bus.

  3. Le parcours de la partie client : un facade au-dessus de la base (2016-2017). (En cours…)

  4. L'histoire des véritables microservices. (2018-2019). (En cours…)

  5. La découpe finie du monolithe et la stabilisation de l'architecture. (En cours…)

L'architecture initiale

En 2011, l'architecture Dodo IS ressemblait à ceci :

L'histoire de l'architecture Dodo IS : une première monolithique

Le premier module de l'architecture — la prise de commande. Le processus commercial était le suivant :

  • le client appelle la pizzeria ;

  • le manager répond au téléphone ;

  • il prend la commande par téléphone ;

  • parallèlement, il la saisit dans l'interface de prise de commande : les informations sur le client, les détails de la commande, l'adresse de livraison sont prises en compte. 

L'interface du système d'information ressemblait à peu près à ça…

La première version d'octobre 2011 :

Légèrement améliorée en janvier 2012

Système d'information Dodo Pizza Restaurant de Livraison

Les ressources pour le développement du premier module de prise de commande étaient limitées. Il fallait faire beaucoup, rapidement, avec peu de personnel. Peu de personnel — c'est 2 développeurs qui ont posé les bases de tout le système futur.

Leur première solution a déterminé le sort ultérieur de la pile technologique :

  • Backend sur ASP.NET MVC, langage C#. Les développeurs étaient des dotnetters, cette pile leur était familière et agréable.

  • Frontend sur Bootstrap et JQuery : interfaces utilisateur avec des styles et des scripts personnalisés. 

  • Base de données MySQL : sans frais de licence, facile à utiliser.

  • Serveurs sous Windows Server, car .NET ne pouvait alors fonctionner que sous Windows (nous ne parlerons pas de Mono).

Physiquement, cela se traduit par un « dédié chez l'hébergeur ». 

Architecture de l'application de traitement des commandes

À cette époque, tout le monde parlait déjà de microservices, et le SOA était utilisé pendant environ 5 ans dans des projets majeurs, par exemple, WCF est sorti en 2006. Mais à l'époque, une solution fiable et éprouvée a été choisie.

Voici.

L'histoire de l'architecture Dodo IS : une première monolithique

Asp.Net MVC est un Razor qui génère, à la demande depuis un formulaire ou un client, une page HTML avec un rendu côté serveur. Côté client, CSS et scripts JS affichent les informations et, si nécessaire, exécutent des requêtes AJAX via JQuery.

Les requêtes sur le serveur sont traitées dans des classes *Controller, où la méthode effectue le traitement et la génération de la page HTML finale. Les contrôleurs effectuent des requêtes vers une couche de logique, appelée *Services. Chacun des services était responsable d'un aspect de l'entreprise :

  • Par exemple, DepartmentStructureService fournissait des informations sur les pizzerias, selon les départements. Un département est un groupe de pizzerias sous la gestion d'un franchisé.

  • ReceivingOrdersService acceptait et calculait la composition de la commande.

  • Et SmsService envoyait des SMS en appelant des services d'API pour l'envoi de SMS.

Les services traitaient les données de la base, stockaient la logique métier. Chaque service avait un ou plusieurs *Repository avec le nom approprié. Ils contenaient déjà des requêtes aux procédures stockées dans la base de données et une couche de mappers. La logique métier se trouvait principalement dans les procédures stockées, surtout celles qui généraient des données de rapport. L'ORM n'était pas utilisé, tout le monde s'appuyait sur des SQL écrits à la main. 

Il y avait aussi une couche de modèle de domaine et des classes utilitaires générales, par exemple, la classe Order qui stockait la commande. Là, dans la couche, se trouvait un helper pour transformer le texte d'affichage selon la devise choisie.

Tout cela peut être représenté par le modèle suivant :

L'histoire de l'architecture Dodo IS : une première monolithique

Parcours de commande

Considérons le parcours simplifié de la création d'une telle commande.

L'histoire de l'architecture Dodo IS : une première monolithique

À l'origine, le site était statique. Il contenait des prix, et en haut figurait un numéro de téléphone avec une inscription « Vous voulez une pizza ? Appelez au numéro et commandez ». Pour passer la commande, nous devions mettre en place un simple flux : 

  • Le client entre sur le site statique avec les prix, choisit des produits et appelle le numéro indiqué sur le site.

  • Le client énonce les produits qu'il souhaite ajouter à la commande.

  • Il donne son adresse et son nom.

  • L'opérateur prend la commande.

  • La commande s'affiche dans l'interface des commandes reçues.

Tout commence par l'affichage du menu. Un utilisateur opérateur connecté ne peut prendre qu'une seule commande à la fois. C'est pourquoi le panier draft peut être conservé dans sa session (la session de l'utilisateur est conservée en mémoire). Il contient un objet Cart, où se trouvent les produits et les informations sur le client.

Le client mentionne le produit, l'opérateur clique sur + à côté du produit, et une requête est envoyée au serveur. Les informations sur le produit sont extraites de la base de données et ajoutées au panier.

L'histoire de l'architecture Dodo IS : une première monolithique

Remarque. Oui, ici il est possible de ne pas extraire le produit de la base, mais de le transmettre depuis le frontend. Mais pour la clarté, j'ai montré le chemin depuis la base. 

Ensuite, nous saisissons l'adresse et le nom du client. 

L'histoire de l'architecture Dodo IS : une première monolithique

En cliquant sur « Créer une commande » :

  • La requête est envoyée à OrderController.SaveOrder().

  • Nous récupérons le Cart de la session, où se trouvent les produits dans la quantité souhaitée.

  • Nous complétons le Cart avec les informations sur le client et les transmettons à la méthode AddOrder de la classe ReceivingOrderService, où elles sont enregistrées dans la base. 

  • Il existe des tables dans la base pour la commande, le contenu de la commande et le client, et elles sont toutes liées.

  • L'interface d'affichage de la commande se met à jour et extrait les dernières commandes et les affiche.

Nouveaux modules

La prise de commande était importante et nécessaire. On ne peut pas créer une entreprise de vente de pizza sans un système de prise de commandes. Ainsi, le système a commencé à se développer fonctionnellement — environ entre 2012 et 2015. Pendant cette période, de nombreux différents blocs du système sont apparus, que je vais appeler modules, contrairement au concept de service ou de produit. 

Un module est un ensemble de fonctions regroupées autour d'un objectif commercial commun. Ils se trouvent physiquement dans une même application.

Les modules peuvent être considérés comme des blocs du système. Par exemple, c'est le module de rapports, les interfaces d'administration, le tracker de produits en cuisine, l'autorisation. Ce sont tous des interfaces différentes pour les utilisateurs, certaines ayant même des styles visuels variés. Tout cela se fait dans le cadre d'une seule application, d'un même processus fonctionnel. 

Techniquement, les modules étaient formalisés comme des Areas (cette idée persiste même dans asp.net core). Il y avait des fichiers séparés pour le frontend, les modèles, ainsi que leurs propres classes de contrôleurs. En fin de compte, le système s'est transformé de cette manière…

L'histoire de l'architecture Dodo IS : une première monolithique

…en ceci :

L'histoire de l'architecture Dodo IS : une première monolithique

Certains modules sont des sites distincts (projet exécutable), en raison d'un ensemble de fonctionnalités totalement distinctes et partiellement à cause d'un développement plus ciblé. Ce sont :

  • Site — la première version du site dodopizza.ru.

  • Exporter: exportation des rapports de Dodo IS pour 1C. 

  • Personnel — espace personnel de l'employé. Développé séparément avec son propre point d'entrée et un design distinct.

  • fs — projet pour l'hébergement de statiques. Nous nous en sommes ensuite éloignés en transférant toute la statique vers CDN Akamai. 

Les autres blocs se trouvaient dans l'application BackOffice. 

L'histoire de l'architecture Dodo IS : une première monolithique

Explication des noms :

  • Caissier — Caisse du restaurant.

  • ShiftManager — interfaces pour le rôle de « Manager de service » : statistiques opérationnelles sur les ventes de la pizzeria, possibilité de mettre des produits sur liste noire, modifier les commandes.

  • OfficeManager — interfaces pour le rôle de « Gérant de pizzeria » et « Franchisé ». Recueille des fonctions pour configurer la pizzeria, ses promotions, la gestion des employés, et les rapports.

  • PublicScreens — interfaces pour les téléviseurs et tablettes suspendus dans les pizzerias. Les téléviseurs affichent le menu, des informations publicitaires, le statut de la commande lors de la remise. 

Ils ont utilisé une couche de services commune, un bloc commun de classes de domaine Dodo.Core, ainsi qu'une base de données partagée. Parfois, il y avait également des références mutuelles. Y compris certains sites, comme dodopizza.ru ou personal.dodopizza.ru, accédaient aux services communs.

Lors de l'apparition de nouveaux modules, ils ont essayé de réutiliser au maximum le code des services, des procédures stockées et des tables déjà créés dans la base. 

Pour mieux comprendre l'ampleur des modules développés dans le système, voici un schéma de 2012 avec les plans de développement :

L'histoire de l'architecture Dodo IS : une première monolithique

D'ici 2015, tout ce qui était sur le schéma et même plus était en production.

  • La prise de commande s'est transformée en un bloc séparé du Centre de Contact, où les commandes sont prises par un opérateur.

  • Des écrans publics avec le menu et des informations sont apparus, suspendus dans les pizzerias.

  • En cuisine, il y a un module qui reproduit automatiquement le message vocal « Nouvelle pizza » lors de la réception d'une nouvelle commande, et imprime également un bon de livraison pour le livreur. Cela simplifie considérablement les processus en cuisine, permettant aux employés de ne pas être distraits par un grand nombre d'opérations simples.

  • Le bloc de livraison est devenu une Caisse de Livraison distincte, où la commande était remise au livreur qui s'était préalablement inscrit pour son service. Son temps de travail était pris en compte pour le calcul de son salaire. 

Parallèlement, de 2012 à 2015, plus de 10 développeurs sont apparus, 35 pizzerias ont ouvert, un système a été déployé en Roumanie et des sites ont été préparés aux États-Unis. Les développeurs ne s'occupaient plus de toutes les tâches, mais étaient répartis en équipes, chacune se spécialisant dans une partie du système. 

Problèmes

Ceci en partie en raison de l'architecture (mais pas seulement).

Chaos dans la base

Une base — c'est pratique. On peut atteindre la cohérence, grâce à des moyens intégrés dans les bases de données relationnelles. Travailler avec elle est habituel et facile, surtout si elle contient peu de tables et peu de données.

Mais au bout de 4 ans de développement, la base comptait environ 600 tables, 1500 procédures stockées, dont beaucoup contenaient encore de la logique. Malheureusement, les procédures stockées n'apportent pas d'avantage particulier lorsqu'on travaille avec MySQL. Elles ne sont pas mises en cache par la base, et l'intégration de la logique complique le développement et le débogage. La réutilisation du code est également problématique.

De nombreuses tables n'avaient pas d'index appropriés., tandis que d'autres, au contraire, avaient un très grand nombre d'index, ce qui compliquait l'insertion. Il a fallu modifier environ 20 tables — la transaction de création de commande pouvait prendre entre 3 et 5 secondes. 

Les données dans les tables n'étaient pas toujours sous la forme la plus appropriée.. Parfois, il était nécessaire de procéder à une dénormalisation. Une partie des données régulièrement reçues était dans une colonne sous forme de structure XML, ce qui augmentait le temps d'exécution, allongeait les requêtes et compliquait le développement.

Des requêtes très variées étaient effectuées sur les mêmes tables.. Les tables populaires, comme celle mentionnée orders ou la table pizzeria, en souffraient particulièrement. Elles étaient utilisées pour afficher des interfaces opérationnelles en cuisine, pour l'analyse. Le sitedodopizza.ruy accédait également, avec un grand nombre de requêtes pouvant arriver à tout moment. 

Les données n'étaient pas agrégées et beaucoup de calculs se faisaient à la volée avec les moyens de la base. Cela engendrait des calculs supplémentaires et une charge supplémentaire. 

Souvent, le code accédait à la base alors qu'il aurait pu s'en passer. Parfois, il manquait des opérations en masse, parfois il aurait fallu répartir une requête en plusieurs via le code pour accéléra et améliorer la fiabilité. 

Cohésion et embrouillamin dans le code

Les modules qui auraient dû répondre à leur secteur d'activité ne le faisaient pas correctement.. Certains d'entre eux avaient des duplications dans les fonctionnalités des rôles. Par exemple, le marketeur local, chargé de l'activité marketing du réseau dans sa ville, devait utiliser à la fois l'interface « Admin » (pour créer des promotions) et l'interface « Manager de Bureau » (pour visualiser l'impact des promotions sur les affaires). Bien sûr, les deux modules utilisaient en interne le même service qui gérait les promotions bonus.

Les services (classes au sein d'un même projet monolithique) pouvaient s'appeler mutuellement pour enrichir leurs données.

Avec les classes-modèles qui stockent les données, le travail dans le code était varié. Parfois, il y avait des constructeurs permettant d'indiquer les champs obligatoires. D'autres fois, cela se faisait via des propriétés publiques. Bien sûr, l'obtention et la transformation des données de la base étaient diversifiées. 

La logique résidait soit dans les contrôleurs, soit dans les classes de services. 

Ce sont des problèmes apparemment insignifiants, mais ils ralentissaient fortement le développement et diminuaient la qualité, entraînant instabilité et erreurs. 

La complexité d'un grand développement

Des difficultés sont également survenues dans le développement même. Il fallait créer différentes parties du système en parallèle. Satisfaire les besoins de chaque composant dans un code unifié devenait de plus en plus difficile. Il n'était pas simple de parvenir à un accord et de satisfaire tous les composants en même temps. À cela s'ajoutaient des limitations en matière de technologies, notamment concernant la base de données et le front-end. Il fallait abandonner jQuery en faveur de frameworks de haut niveau, surtout pour les services client (site web).

Dans certaines parties du système, des bases de données plus adaptées auraient pu être utilisées. Par exemple, nous avons par la suite eu un précédent de migration de Redis vers CosmosDB pour le stockage du panier de commande. 

Les équipes et les développeurs, s'occupant de leur domaine, voulaient clairement plus d'autonomie pour leurs services, tant en matière de développement que de déploiement. Des conflits lors des fusions, des problèmes lors des releases. Si pour 5 développeurs ce problème était mineur, il devenait bien plus sérieux pour 10, et encore plus avec la croissance prévue. De plus, le développement d'une application mobile devait suivre (il a démarré en 2017, et en 2018 il y a eu une forte chute). 

Différentes parties du système nécessitaient différents niveaux de stabilité., mais en raison de la forte interconnexion du système, nous n'avons pas pu garantir cela. Une erreur lors du développement d'une nouvelle fonctionnalité dans l'interface d'administration a pu se répercuter sur le processus de commande sur le site, car le code est commun et réutilisé, tout comme la base de données et les données.

Il aurait probablement été possible de ne pas permettre ces erreurs et ces problèmes dans le cadre d'une architecture modulaire monolithique : en séparant les responsabilités, en effectuant le refactoring tant du code que de la base de données, en distinguant clairement les couches, et en surveillant la qualité chaque jour. Cependant, les choix architecturaux effectués et l'accent mis sur l'expansion rapide des fonctionnalités du système ont conduit à des problèmes de stabilité.

Comment le blog La force de l'esprit a affecté les caisses dans les restaurants

Si la croissance du réseau de pizzerias (et la charge) avait continué au même rythme, il aurait bientôt été si difficile de gérer les baisses que le système ne pourrait pas se rétablir. Une histoire illustre bien les problèmes que nous avons commencé à rencontrer en 2015. 

Dans le blog «La force de l'esprit», il y avait un widget qui affichait les données de revenus annuels pour l'ensemble du réseau. Le widget se connectait à l'API publique de Dodo, qui fournit ces données. Cette statistique est désormais disponible sur http://dodopizzastory.com/. Le widget s'affichait sur chaque page et faisait des requêtes toutes les 20 secondes. La requête était envoyée à api.dodopizza.ru et demandait :

  • le nombre de pizzerias dans le réseau ;

  • le revenu total du réseau depuis le début de l'année ;

  • le revenu d'aujourd'hui.

La requête sur la statistique des revenus allait directement à la base de données et commençait à interroger les données des commandes, à agréger les informations à la volée et à fournir le montant. 

Dans cette même table des commandes, les caisses des restaurants importaient la liste des commandes reçues pour aujourd'hui, et de nouvelles commandes y étaient également ajoutées. Les caisses faisaient leurs requêtes toutes les 5 secondes ou lors du rechargement de la page.

Le schéma était le suivant :

L'histoire de l'architecture Dodo IS : une première monolithique

Un jour d'automne, Fédor Ovtchinnikov a écrit un long et populaire article sur son blog. Le blog a attiré de nombreuses personnes qui ont commencé à lire attentivement. Pendant que chaque visiteur lisait l'article, le widget de revenus fonctionnait correctement et faisait des requêtes à l'API toutes les 20 secondes.

L'API a appelé une procédure stockée pour calculer le montant total de toutes les commandes depuis le début de l'année dans l'ensemble du réseau de pizzerias. L'agrégation se faisait à partir de la table des commandes, qui est très populaire. Toutes les caisses de tous les restaurants ouverts à ce moment y accédaient. Les caisses ont cessé de répondre, les commandes n'étaient plus prises en compte. De plus, elles n'étaient pas prises en charge depuis le site, n'apparaissaient pas sur le suiveur, le responsable de quart ne pouvait pas les voir dans son interface. 

Ce n'est pas la seule histoire. À l'automne 2015, la charge sur le système était critique chaque vendredi. Nous avons désactivé plusieurs fois l'API publique, et une fois, nous avons même dû désactiver le site, car rien ne fonctionnait plus. Il y avait même une liste de services avec un ordre de coupure en cas de charges lourdes.

C'est à partir de ce moment que commence notre lutte contre les charges et pour la stabilisation du système (de l'automne 2015 à l'automne 2018). C'est à ce moment-là que s'est produite laGrande chute. D'autres pannes se sont également produites de temps à autre, certaines étaient très sensibles, mais nous pouvons considérer que la période d'instabilité générale est maintenant derrière nous.

Croissance rapide des affaires

Pourquoi n'a-t-on pas pu « faire bien dès le départ » ? Il suffit de regarder les graphiques suivants.

L'histoire de l'architecture Dodo IS : une première monolithique

De plus, en 2014-2015, une ouverture en Roumanie a eu lieu et une ouverture aux États-Unis était en préparation.

Le réseau se développait très rapidement, de nouveaux pays s'ouvraient, de nouveaux formats de pizzerias apparaissaient, par exemple, une pizzeria dans un food court a ouvert. Tout cela nécessitait une attention considérable à l'expansion des fonctionnalités de Dodo IS. Sans toutes ces fonctionnalités, sans le suivi en cuisine, la comptabilisation des produits et des pertes dans le système, l'affichage des commandes dans la salle du food court, il est peu probable que nous discutions maintenant de l'architecture « correcte » et de l'approche « juste » au développement.

En outre, des obstacles à un réexamen opportun de l'architecture et à l'attention portée aux problèmes techniques étaient la crise de 2014. De telles choses frappent durement les possibilités de croissance des équipes, surtout pour une jeune entreprise comme Dodo Pizza.

Solutions rapides qui ont aidé

Les problèmes nécessitaient une solution. En gros, les solutions peuvent être divisées en 2 groupes :

  • Rapides, qui éteignent le feu et donnent une petite marge de sécurité, nous gagnant ainsi du temps pour les changements.

  • Systématiques et, donc, longues. La réingénierie de plusieurs modules, la séparation de l'architecture monolithique en services distincts (la plupart d'entre eux sont plutôt des macroservices que des microservices et il y a à ce sujet) rapport d'Andrei Morevsky). 

La liste sèche des changements rapides est la suivante :

Scale up master de base

Bien sûr, la première chose à faire pour lutter contre les charges est d'augmenter la puissance du serveur. Cela a été fait pour le master de base et pour les serveurs web. Malheureusement, cela n'est possible que jusqu'à un certain point, au-delà, cela devient trop coûteux.

Depuis 2014, nous sommes passés à Azure, à ce sujet, nous avions également écrit à l'époque dans l'article «Comment Dodo Pizza livre des pizzas grâce au cloud Microsoft Azure». Mais après une série d'augmentations de serveurs pour la base, nous avons été limités par les coûts. 

Répliques de la base en lecture

Deux répliques ont été créées pour la base :

ReadReplica pour les requêtes sur les répertoires. Utilisé pour la lecture des répertoires, tels que, villes, rues, pizzerias, produits (domaine à changement lent), et dans les interfaces où un léger délai est acceptable. Il y avait 2 de ces répliques, nous avons assuré leur disponibilité de la même manière que celle du master.

ReadReplica pour les requêtes sur les rapports. Cette base avait une disponibilité inférieure, mais tous les rapports y ont accès. Bien qu'ils aient des requêtes lourdes sur d'énormes recalculs de données, cela n'affecte pas la base principale et les interfaces opérationnelles. 

Caches dans le code

Il n'y avait nulle part de caches dans le code (au total). Cela entraînait des requêtes supplémentaires, pas toujours nécessaires, dans la base chargée. Les caches étaient d'abord en mémoire, ainsi que sur un service de cache externe, qui était Redis. Tout était invalidé dans le temps, les paramètres indiqués dans le code.

Plusieurs serveurs pour le backend

Le backend de l'application devait aussi être mis à l'échelle pour supporter des charges accrues. Il était nécessaire de transformer un serveur iis en un cluster. Nous avons déplacé la session des applications de la mémoire vers RedisCache, ce qui a permis de créer plusieurs serveurs derrière un simple répartiteur de charge en round robin. Au début, le même Redis a été utilisé que pour les caches, puis il a été réparti sur plusieurs. 

En fin de compte, l'architecture s'est complexifiée…

L'histoire de l'architecture Dodo IS : une première monolithique

…mais une partie de la tension a pu être relâchée.

Ensuite, il fallait retravailler les composants chargés, ce que nous avons commencé à faire. Nous en parlerons dans la prochaine partie.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster