Be Software Architecture Wise
Be Wise Apps

Read in English · Leer en español · Auf Deutsch lesen

Section 11.6 sur 11.8 · Avancé · 25 min de lecture

Moderniser les systèmes hérités : strangler fig et migration incrémentale

Leçon vidéo

5 min · narré en anglais, avec sous-titres
Écouter seulement (audio)
Lire la transcription

Tout système qui réussit finit par devenir un système hérité. Dans cette leçon, vous allez découvrir pourquoi les grandes réécritures échouent si souvent, et comment moderniser plutôt morceau par morceau.

Imaginez une ville qui a besoin d’un nouveau pont. Démolir d’abord l’ancien couperait la ville du monde pendant des années. Alors les ingénieurs construisent le nouveau pont à côté de l’ancien. Ils l’ouvrent une voie à la fois, et y font passer la circulation progressivement. Si une nouvelle voie pose problème, la circulation revient simplement sur l’ancien pont pendant un moment. Et ils ne démolissent l’ancien pont que lorsque plus rien ne l’emprunte. Moderniser un vieux logiciel fonctionne mieux de la même manière.

Un système hérité fait encore quelque chose de précieux, mais il est devenu difficile, risqué ou coûteux à modifier. La plupart des systèmes hérités sont des systèmes qui ont réussi. La réponse évidente est de tout recommencer, et c’est généralement une erreur. Dans une réécriture big bang, la valeur n’arrive qu’à la fin, si bien que le métier attend des mois ou des années, et perd patience. L’ancien système continue de changer pendant que le nouveau est construit : la cible bouge. Des comportements cachés se perdent. Le vieux code paraît désordonné en partie parce qu’il contient des années de corrections pour des situations dont personne ne se souvient. Joel Spolsky l’a souligné en 2000, à propos de la réécriture de Netscape. Et le déplacement des données, puis la bascule vers le nouveau système, viennent en dernier, au moment où ils sont les plus difficiles. En 2018, la migration de plateforme de la banque britannique TSB a très mal tourné, et les régulateurs lui ont ensuite infligé une amende de quarante-huit virgule soixante-cinq millions de livres.

Le strangler fig, nommé par Martin Fowler, remplace un système morceau par morceau pendant qu’il continue de fonctionner. D’abord, interceptez : placez une façade de routage devant le système hérité. Au début, elle envoie tout à l’ancien système. Puis reconstruisez une tranche de comportement, ici le suivi des commandes, sous la forme d’un nouveau composant, et changez une règle de routage pour l’y envoyer. S’il se comporte mal, rebasculez la règle. Recommencez, tranche par tranche, avec une synchronisation temporaire des données, jusqu’à ce qu’il ne reste plus que la facturation dans l’ancien système. Enfin, le système hérité est éteint. La façade, les adaptateurs et les tâches de synchronisation relèvent de l’architecture de transition, construite pour être jetée. Mesurez donc l’avancement à ce qui a été éteint.

Les données sont la partie la plus difficile. À chaque étape, un seul système exactement doit être le système de référence. Au début, le système hérité possède les commandes, et la capture des données modifiées copie ses lignes vers le nouveau stockage, en passant par une couche anticorruption qui les traduit. Plus tard, le sens s’inverse. Le nouveau service de commandes possède les commandes, et ses événements sont retraduits en lignes héritées pour les modules, comme la facturation, qui n’ont pas encore migré. Les données circulent dans un seul sens à la fois.

Voici comment la plateforme a sorti son module de commandes du monolithe. D’abord, fixez les résultats visés : des mises en production quotidiennes et indépendantes pour les commandes, trois fois le pic actuel du vendredi, et aucune commande perdue ou dupliquée. Des requêtes de production enregistrées sont devenues des tests qui capturent le comportement actuel, bizarreries comprises. Ensuite, placez une API gateway devant le monolithe, qui lui route tout. Elle ne change aucun comportement, et l’équipe s’exerce à basculer les routes. Troisièmement, construisez le nouveau service dans l’ombre, pendant que le système hérité reste le système de référence. Lors d’une exécution en parallèle, le taux de divergence part d’environ deux pour cent, et il est ramené sous zéro virgule zéro un pour cent avant d’aller plus loin. Quatrièmement, déplacez les lectures du suivi des commandes : cinq pour cent du trafic, puis vingt-cinq, puis la totalité. Le retour arrière tient en une règle de routage. Cinquièmement, déplacez les écritures, une ville à la fois : une ville pilote, puis toutes les villes en six semaines. Un adaptateur transforme les événements du nouveau service en lignes héritées, pour que la facturation et le reporting continuent de fonctionner. Enfin, migrez trois années d’historique. Quand les anciennes tables de commandes n’ont reçu aucune lecture pendant trente jours, supprimez l’adaptateur, le pipeline et l’ancien code. Ce n’est qu’alors que la migration est terminée.

Le strangler fig agit à la périphérie, là où les requêtes peuvent être routées. Branch by abstraction agit au cœur d’une seule base de code. Placez une interface devant l’ancien composant, faites-y passer les appelants, construisez la nouvelle implémentation derrière elle, basculez progressivement, puis supprimez l’ancien code. Une exécution en parallèle prouve que le nouveau code se comporte comme l’ancien. Les deux reçoivent les mêmes entrées, mais c’est toujours l’ancien résultat qui est renvoyé à l’utilisateur, et les écarts sont enregistrés. Le nouveau chemin ne doit avoir aucun effet de bord, comme débiter une carte deux fois.

La section complète présente les sept R, le dark launching et la manière de migrer les données en toute sécurité. Ensuite, dans la section onze point sept, nous verrons le leadership et l’influence. À très vite.

Ce qu’il faut savoir

Ce qui fait d’un système un système hérité

Un système hérité (legacy) n’est pas simplement un vieux système. C’est un système qui fait encore quelque chose de précieux, souvent quelque chose sans quoi l’organisation ne pourrait pas fonctionner, mais qui est devenu difficile, risqué ou coûteux à modifier. Michael Feathers, dans Working Effectively with Legacy Code (2004), en a donné une définition volontairement provocatrice : le code hérité est du code sans tests, car sans tests chaque changement est un pari. Les autres causes courantes sont des compétences rares (un langage ou une plateforme que peu de gens connaissent encore), une technologie qui n’est plus supportée (un système d’exploitation ou une base de données arrivé en fin de support chez son fournisseur), une connaissance perdue (les personnes qui comprenaient la conception sont parties) et une dette technique accumulée dans la structure elle-même, le type coûteux décrit à la section 1.7.

Il faut le dire clairement : la plupart des systèmes hérités sont des systèmes qui ont réussi. Ils ont survécu parce qu’ils fonctionnaient et rapportaient de l’argent. Ce succès explique aussi pourquoi ils sont difficiles à remplacer : des années d’utilisation réelle y ont incorporé des milliers de petites décisions, de corrections et de règles métier qui ne sont écrites nulle part, sauf dans le code et dans les habitudes des personnes qui l’utilisent.

La modernisation a donc besoin d’une raison qui puisse s’énoncer en termes métier. Parmi les bonnes raisons : le coût ou le risque du changement bloque des objectifs métier importants ; la plateforme perd le support du fournisseur ou ses correctifs de sécurité ; les compétences pour la maintenir deviennent introuvables ; les coûts d’exploitation sont hors de proportion avec la valeur ; ou le système ne peut pas répondre à de nouvelles exigences d’échelle, de disponibilité ou de réglementation. Les mauvaises raisons sont la mode, l’ennui et la conviction qu’une nouvelle technologie est automatiquement meilleure. La section 11.8 montre comment transformer une bonne raison en analyse de rentabilité.

Pourquoi les réécritures échouent

La réponse évidente à un vieux système pénible est de tout recommencer. En avril 2000, Joel Spolsky a publié un essai, « Things You Should Never Do, Part I », soutenant que la décision de tout réécrire de zéro compte parmi les pires erreurs stratégiques qu’un éditeur de logiciels puisse commettre. Son exemple était Netscape, dont le navigateur est resté près de trois ans sans version majeure pendant la réécriture du code, période durant laquelle les concurrents ont pris le marché. Son argument central portait sur la connaissance : le vieux code paraît désordonné en partie parce qu’il contient des années de corrections de bugs pour des situations dont personne ne se souvient, et le jeter, c’est jeter aussi ces corrections.

Les raisons pour lesquelles une réécriture big bang échoue sont aujourd’hui bien comprises. La valeur n’arrive qu’à la fin, si bien que le métier attend des mois ou des années et perd patience. L’ancien système continue de changer pendant que le nouveau est construit : la cible bouge, et l’organisation soit gèle l’ancien système (et l’activité avec lui), soit paie pour faire chaque changement deux fois. Des comportements cachés sont oubliés, car personne ne peut énumérer toutes les règles que l’ancien système applique. Les estimations sont optimistes, car les fonctionnalités visibles ne sont qu’une fraction du travail réel. La migration des données et la bascule sont laissées pour la fin, au moment où elles sont les plus difficiles et les plus risquées. Et l’équipe se divise entre un groupe prestigieux chargé du nouveau système et un groupe démoralisé chargé de l’ancien. L’effet du second système de Fred Brooks, tiré de The Mythical Man-Month (1975), ajoute un risque supplémentaire : les concepteurs qui remplacent un système qu’ils connaissent ont tendance à charger le nouveau de toutes les idées que l’ancien ne pouvait pas accueillir.

Les grandes migrations qui basculent en une seule étape peuvent aussi échouer de façon très publique. En avril 2018, la banque britannique TSB a transféré les données et les services de ses clients vers une nouvelle plateforme informatique ; la plateforme a immédiatement connu de graves problèmes, toutes ses agences et une part importante de ses 5,2 millions de clients ont été touchées, et la banque n’a retrouvé un fonctionnement normal qu’en décembre 2018. En décembre 2022, la Financial Conduct Authority et la Prudential Regulation Authority ont infligé à TSB des amendes d’un total de 48,65 millions de livres sterling, estimant qu’elle n’avait pas correctement planifié, organisé et contrôlé la migration, ni géré les risques liés à son fournisseur externalisé.

Les praticiens ne s’accordent pas tous à dire que les réécritures sont toujours une erreur. Une réécriture peut être le bon choix quand le système est petit, que son comportement est bien spécifié et couvert par des tests, que la plateforme est vraiment dans une impasse, ou que le métier lui-même a tellement changé que l’ancien comportement n’est plus souhaité. Même alors, les réécritures les plus sûres sont livrées de façon incrémentale, l’ancien et le nouveau système fonctionnant côte à côte, ce qui est l’objet du reste de cette section.

Stratégies de migration : les R

Quand une organisation passe en revue tout un portefeuille d’applications, par exemple avant de quitter un centre de données, elle a besoin d’un vocabulaire pour décider quoi faire de chacune. Gartner a décrit cinq options de ce type au début des années 2010. En 2016, Stephen Orban, d’AWS, a publié six stratégies, et AWS en a ensuite ajouté une septième, ce qui donne les sept R utilisés aujourd’hui dans ses recommandations de migration. D’autres fournisseurs cloud et cabinets de conseil utilisent des listes semblables, avec des noms légèrement différents. La liste est surtout utile pour rappeler que reconstruire n’est qu’une option parmi plusieurs, et généralement la plus coûteuse.

Les sept R (d’après AWS Prescriptive Guidance)

StratégieSignificationConvient quandChangement d’architecture
RetirerÉteindre l’application et archiver ses donnéesPersonne ne l’utilise, ou sa fonction existe déjà ailleursAucun ; supprimer les dépendances envers elle
ConserverLa laisser où elle est pour l’instant et y revenir plus tardAucune raison pressante de la déplacer, ou d’autres travaux doivent passer d’abordAucun pour l’instant
Réhéberger (lift and shift)La déplacer sans modification vers une nouvelle infrastructure, comme des machines virtuelles dans le cloudLe facteur principal est de quitter rapidement un centre de donnéesAucun ; les problèmes la suivent
RelocaliserDéplacer des plateformes entières, comme un parc virtualisé, sans modifier les applicationsUne plateforme compatible existe à destinationAucun
RacheterLa remplacer par un produit, souvent un logiciel en tant que serviceLa capacité est générique et un bon produit existeIntégration et migration des données ; perte de la personnalisation
Changer de plateformeLa déplacer avec quelques optimisations ciblées, comme une base de données géréeDes gains peu coûteux sont possibles sans refonteFaible et local
Refactoriser ou réarchitecturerLa repenser, souvent de façon incrémentale, pour répondre à de nouveaux besoins de qualité ou métierLa capacité est cœur de métier, change souvent et sa structure bloque le changementImportant ; utiliser les patrons incrémentaux ci-dessous

Guide de décision

Guide de décision : choisir une stratégie pour une application

Posez ces questions pour chaque application du portefeuille, dans l’ordre. Les réponses viennent des données d’utilisation, du responsable métier et de l’architecture, pas des préférences technologiques.

  1. Quelqu’un a-t-il encore besoin de ce que fait cette application ?
    OuiPassez à la question suivante.
    NonRetirez-la, après avoir archivé les données que vous êtes légalement tenu de conserver.
  2. Existe-t-il un produit qui répond au besoin, pour une capacité qui ne différencie pas l’entreprise ?
    OuiEnvisagez le rachat, et prévoyez un budget pour l’intégration et la migration des données.
    NonPassez à la question suivante.
  3. Existe-t-il une raison pressante d’agir maintenant, comme la fin du support, la sortie d’un centre de données, le coût ou un changement bloqué ?
    OuiPassez à la question suivante.
    NonConservez-la et fixez une date pour réexaminer la décision.
  4. Le problème principal tient-il à l’endroit où elle s’exécute plutôt qu’à la manière dont elle est construite ?
    OuiRéhébergez-la ou relocalisez-la, ou changez-la de plateforme là où un service géré apporte des gains peu coûteux.
    NonPassez à la question suivante.
  5. S’agit-il d’une capacité cœur de métier dont la structure bloque des changements fréquents et précieux ?
    OuiRefactorisez-la ou réarchitecturez-la de façon incrémentale, avec un strangler fig et branch by abstraction.
    NonChangez-la de plateforme et contenez-la : ajoutez des tests, corrigez les pires points chauds et évitez une grande refonte.

Le strangler fig

Martin Fowler a nommé le patron incrémental le plus utilisé d’après les figuiers étrangleurs (strangler figs) qu’il a vus dans les forêts tropicales du Queensland en 2001 : des plantes grimpantes qui germent dans les branches d’un arbre hôte, poussent jusqu’au sol et entourent peu à peu l’hôte jusqu’à ce qu’il meure et que le figuier tienne debout seul. Il l’a décrit pour la première fois sous le nom de strangler application en 2004, l’a ensuite renommé strangler fig application et a profondément révisé l’article en 2024. La version logicielle, généralement appelée simplement strangler fig (figuier étrangleur), maintient l’ancien système en fonctionnement pendant que de nouveaux composants poussent autour de lui et reprennent son comportement un morceau à la fois, jusqu’à ce que l’ancien système ne fasse plus rien et puisse être éteint.

Le mécanisme comporte trois parties. D’abord, une couche d’interception est placée devant le système hérité : un reverse proxy, une API gateway ou une façade de routage par laquelle passent toutes les requêtes. Au départ, elle envoie tout à l’ancien système et ne change rien. Ensuite, une tranche de comportement est choisie le long d’un point d’articulation (seam : un endroit où le comportement peut être redirigé sans modifier une grande partie de l’ancien code, au sens de Feathers), reconstruite sous la forme d’un nouveau composant et activée en changeant une règle de routage, généralement d’abord pour une petite part du trafic. Enfin, le processus se répète jusqu’à ce que le système hérité ne traite plus rien ; il est alors retiré. Tout au long, la couche de routage rend chaque étape réversible : si le nouveau composant se comporte mal, la règle est rebasculée.

Le code qui permet à l’ancien et au nouveau de coexister (la façade de routage, les adaptateurs, les tâches de synchronisation des données) relève de l’architecture de transition : du logiciel construit pour être jeté. Les équipes rechignent souvent à l’écrire, car cela semble du gaspillage. L’argument de Fowler, et l’expérience qui le sous-tend, est que ce coût achète un risque réduit et une valeur bien plus précoce, ce qui est presque toujours un bon compromis. Le vrai danger est l’inverse : une migration qui cale à mi-chemin, laissant les deux systèmes et tout le code de transition en production indéfiniment. Chaque tranche doit donc retirer quelque chose de l’ancien système, et l’avancement doit se mesurer à ce qui a été éteint, pas seulement à ce qui a été construit.

Ian Cartwright, Rob Horn et James Lewis, de Thoughtworks, ont documenté une famille de techniques apparentées sous le titre Patterns of Legacy Displacement sur martinfowler.com (à partir de 2021). Ils présentent le travail comme quatre activités qui ne forment pas une séquence fixe : comprendre les résultats visés, décomposer le problème en parties, livrer ces parties de façon incrémentale, et faire évoluer l’organisation pour que le nouveau système ne se dégrade pas comme l’ancien. Ils remettent aussi en question le réflexe de reproduire chaque fonctionnalité : une grande partie du comportement d’un système hérité n’est peut-être plus souhaitée, et le vérifier coûte moins cher que de le reconstruire.

Une migration strangler fig en quatre étapes

Quatre étapes d’une migration strangler fig : interposition, 1re tranche, presque tout, retrait1. InterposerClientsFaçade routageMonolithe héritéaucun changement100 % à l’ancien2. 1re trancheClientsFaçade routageHéritéNeufsynchro temporairesuivi au nouveau3. Presque toutClientsFaçade routageAnc.Nouv. servicessynchro temporairereste : facturation4. RetraitClientsFaçade routageNouv. servicesancien système éteint100 % au neuf
Une façade de routage permet au trafic de passer tranche par tranche du système hérité aux nouveaux services, avec une synchronisation temporaire des données, jusqu’à ce que le système hérité puisse être éteint.

Branch by abstraction (branchement par abstraction)

Le strangler fig agit à la périphérie d’un système, là où les requêtes peuvent être routées. Certains remplacements doivent se faire au cœur d’une seule base de code : remplacer un framework de persistance, un moteur de tarification ou une couche de messagerie maison utilisée par des centaines de points d’appel. Branch by abstraction (branchement par abstraction), un nom introduit par Paul Hammant lorsqu’il défendait le développement basé sur le tronc et que Martin Fowler a décrit en 2014, y parvient sans branche de gestion de versions à longue durée de vie. Le système reste livrable à chaque étape, si bien que le changement peut s’étaler sur des semaines ou des mois pendant que d’autres travaux continuent sur la même ligne principale.

Branch by abstraction, pas à pas

  1. Introduire une abstractionCréez une interface devant le composant à remplacer, avec une implémentation qui délègue simplement au code existant. Le comportement ne change pas.
  2. Faire passer les appelants par l’abstractionModifiez progressivement les points d’appel pour qu’ils utilisent l’interface au lieu de l’ancien composant directement. Chaque modification est petite et mise en production normalement.
  3. Construire la nouvelle implémentationÉcrivez le remplaçant derrière la même interface, en le testant avec les mêmes tests et, idéalement, par rapport au comportement en production lors d’une exécution en parallèle.
  4. Basculer progressivementUtilisez un feature flag ou la configuration pour envoyer certains appelants ou certains clients vers la nouvelle implémentation, en augmentant la part à mesure que la confiance grandit.
  5. Supprimer l’ancienne implémentationUne fois que plus rien n’utilise l’ancien code, supprimez-le, et supprimez aussi l’abstraction si elle ne justifie plus sa place.

Prouver l’équivalence : exécution en parallèle et dark launching

La question la plus difficile de toute migration est de savoir si la nouvelle implémentation se comporte vraiment comme l’ancienne. Une exécution en parallèle (parallel run) y répond avec des preuves issues de la production : les deux implémentations reçoivent les mêmes entrées, l’ancienne continue de faire foi et c’est sa réponse qui est renvoyée à l’utilisateur, puis les deux résultats sont comparés et chaque écart est enregistré. GitHub a publié sa bibliothèque Scientist en 2016 exactement dans ce but : elle exécute côte à côte un contrôle et un candidat et signale les divergences ; des outils semblables existent pour de nombreux langages, et la technique est simple à écrire soi-même, comme le montre l’esquisse ci-dessous.

Le dark launching (lancement masqué), que Fowler décrit comme le fait d’appeler un nouveau comportement backend en production sans que les utilisateurs en voient les effets, en est proche. Il sert surtout à tester les performances et la stabilité du nouveau chemin sous une charge réelle avant qu’un utilisateur en dépende, tandis qu’une exécution en parallèle se concentre sur l’exactitude. Tous deux diffèrent d’une canary release (déploiement canari), dans laquelle une petite part des utilisateurs reçoit réellement le nouveau comportement.

Les deux techniques ont leurs pièges. Le candidat ne doit avoir aucun effet de bord visible de l’extérieur : il ne doit pas envoyer un second e-mail, débiter une carte deux fois ni écrire dans la base de données partagée ; les exécutions en parallèle sont donc plus faciles pour les lectures et les calculs, et les écritures exigent un bac à sable ou un stockage séparé. Les comparaisons doivent ignorer les différences légitimes, comme les horodatages, l’ordre et le formatage des nombres à virgule flottante, sinon le rapport des divergences se noie dans le bruit. Et exécuter les deux chemins consomme de la capacité : la part du trafic échantillonnée est donc souvent limitée.

def quote_delivery_fee(order):
    old = legacy_pricing.quote(order)          # still authoritative
    try:
        new = new_pricing.quote(order)         # must have no side effects
        if new.amount_pence != old.amount_pence:
            metrics.increment("pricing.mismatch")
            log.info("pricing mismatch", order_id=order.id,
                     old=old.amount_pence, new=new.amount_pence)
        else:
            metrics.increment("pricing.match")
    except Exception:
        metrics.increment("pricing.candidate_error")   # never hurts the user
    return old
Une exécution en parallèle minimale : le résultat de l’ancien système est toujours renvoyé, tandis que la réponse du candidat est comparée et les divergences comptées.

Les données : la partie la plus difficile

Router les requêtes est relativement facile ; déplacer les données et les garder cohérentes pendant que deux systèmes en partagent la responsabilité, c’est là que la plupart des migrations peinent. La première règle est celle du chapitre 7 sur la propriété des données : pour chaque type de données, à chaque étape de la migration, l’équipe doit pouvoir dire quel système est le système de référence (system of record), quelles copies existent et à quel point chacune peut être périmée. Le diagramme ci-dessous montre l’inversion typique : le système hérité possède d’abord les données et le nouveau composant en lit une copie ; plus tard, le nouveau composant les possède et le système hérité en reçoit une copie pour les modules qui en ont encore besoin.

La capture des données modifiées est la manière habituelle de copier des données hors d’une base de données héritée sans modifier le code hérité : un outil comme Debezium lit le journal des transactions de la base et publie chaque changement sous forme d’événement, le même mécanisme que celui utilisé à la section 6.7 pour relayer une outbox. L’interception d’événements, l’un des patrons de remplacement des systèmes hérités (legacy displacement), agit à l’autre bout : elle capte les requêtes ou messages qui modifient l’état en route vers le système hérité et en transmet des copies (ou, plus tard, les originaux) au nouveau composant. Dans les deux cas, ne laissez jamais les deux systèmes accepter indépendamment des écritures sur les mêmes données avec une synchronisation bidirectionnelle, sauf si vous n’avez pas d’autre choix ; les conflits sont alors presque garantis.

Les données héritées correspondent rarement au nouveau modèle. Les codes de statut sont des entiers aux significations non documentées, une même table sert à trois usages, et les dates sont stockées en heure locale. Une couche anticorruption, le patron de la conception pilotée par le domaine qui protège un modèle de celui d’un voisin, traduit entre les deux pour que les bizarreries du système hérité ne s’infiltrent pas dans le nouveau code. Elle fait partie de l’architecture de transition et doit disparaître avec le système hérité.

La migration finale des données, qui déplace l’historique et effectue la bascule, mérite son propre plan. Profilez d’abord les données héritées pour trouver les doublons, les orphelins et les valeurs invalides. Écrivez explicitement la correspondance et testez-la sur une copie complète, à la taille de la production. Faites un rapprochement après chaque exécution avec des nombres de lignes, des sommes des colonnes monétaires et des sommes de contrôle, pas par des contrôles ponctuels. Répétez la bascule, chronométrez-la et mettez par écrit le retour arrière. Lorsque c’est possible, migrez par cohortes (une région, un segment de clientèle, une gamme de produits) plutôt que tout à la fois, afin qu’un problème ne touche qu’une fraction des clients et vous apprenne quelque chose avant la cohorte suivante.

Le système de référence bascule pendant la migration

La synchronisation des données s’inverse quand le nouveau service de commandes devient le système de référenceÉtape 3 : cmdes à l’ancienFaçade routageMonolithe héritémod. commandesServ. commandeslect. fantômesTables cmdeshéritéesNouvelle BD cmdesécriturescopie lecturesCDC + ACLtraduit lignesÉtape 5 : cmdes au nouveau serviceFaçade routageMonolithe héritéfactur., reportingServ. commandessyst. de référenceTables cmdeshéritéesNouvelle BD cmdesautres fonctionsécritures cmdesAdapt. ACLévts en lignesévts OrderPlaced
Au début, la capture des données modifiées copie les lignes héritées dans le nouveau stockage ; une fois que le nouveau service possède les commandes, ses événements sont retraduits en lignes héritées pour les modules pas encore migrés.

Les leçons de Kill It with Fire

Kill It with Fire: Manage Aging Computer Systems (and Future Proof Modern Ones) (No Starch Press, 2021), de Marianne Bellotti, s’appuie sur son expérience de la modernisation de systèmes publics et commerciaux, y compris de parcs mainframe. Plusieurs de ses thèmes méritent d’être appliqués à toute modernisation.

La modernisation doit se justifier par la valeur qu’elle apporte et le risque qu’elle supprime, pas par l’âge de la technologie ; une vieille technologie bien comprise et qui fonctionne peut être la partie la moins risquée du parc. Les choix technologiques évoluent par cycles : une équipe qui remplace un système uniquement parce que sa conception est passée de mode risque de construire l’héritage de la génération suivante. L’amélioration incrémentale du système existant est généralement plus sûre que le remplacement, et une réécriture complète exige une justification exceptionnellement solide. Enfin, la modernisation est un problème d’organisation autant qu’un problème technique : elle a besoin de premières victoires visibles pour entretenir l’élan et le moral, de critères clairs pour savoir quand elle est terminée, et d’une attention aux incitations qui ont fait se dégrader l’ancien système, faute de quoi le nouveau suivra le même chemin.

Exemple commenté

Exemple commenté : extraire le module de commandes du monolithe par étranglement, en six étapes

Cet exemple revient sur la manière dont la plateforme de livraison de repas a obtenu le service de commandes séparé évalué à la section 11.1. Plus tôt dans son histoire, le monolithe d’origine de la plateforme contenait un module de commandes qui gérait la passation, la modification, l’annulation et le suivi des commandes. Les changements liés aux commandes étaient devenus le principal goulot d’étranglement : ils exigeaient de déployer tout le monolithe, et les pics du vendredi soir ralentissaient toutes les autres fonctionnalités. Quatorze autres modules, dont la facturation, le reporting et les outils du support client, lisaient directement les tables des commandes. L’équipe a planifié une migration incrémentale vers un service de commandes séparé.

1. Fixer les résultats visés et trouver le point d’articulationRésultats visés : des changements liés aux commandes déployables chaque jour et de façon indépendante ; trois fois le pic actuel du vendredi sans effet sur les autres fonctionnalités ; aucune commande perdue ou dupliquée. L’analyse des journaux de requêtes confirme que seul le module de commandes écrit dans les tables des commandes et que tout le trafic des commandes entre par les endpoints /api/orders. L’équipe enregistre plusieurs semaines de requêtes de production et les transforme en tests de caractérisation qui capturent le comportement actuel, bizarreries comprises.
2. Insérer la façade de routageUne API gateway est placée devant le monolithe et, au départ, lui route tout. Mise en production seule, elle ne change aucun comportement ; l’équipe vérifie la latence ajoutée (quelques millisecondes au 95e centile) et s’exerce à basculer les routes. Retour arrière : retirer la gateway du chemin.
3. Construire le nouveau service dans l’ombreLe nouveau service de commandes a son propre stockage. Le système hérité reste le système de référence : la capture des données modifiées copie les lignes de commandes, en passant par une couche anticorruption qui convertit les codes de statut entiers et les dates en heure locale vers le nouveau modèle. Les endpoints de lecture fonctionnent en exécution en parallèle, comparés aux réponses du système hérité ; le taux de divergence part d’environ 2 % (arrondi des frais, fuseaux horaires) et est ramené sous 0,01 % avant d’aller plus loin.
4. Déplacer les lecturesLes lectures du suivi des commandes sont routées vers le nouveau service pour 5 %, puis 25 %, puis la totalité du trafic, en surveillant les taux d’erreur et la latence à chaque étape. Retour arrière : une règle de routage.
5. Déplacer les écritures, une ville à la foisLe nouveau service devient le système de référence pour les commandes passées dans une ville pilote, puis pour toutes les villes en six semaines. Il publie les événements OrderPlaced et OrderCancelled au moyen d’une outbox transactionnelle ; un adaptateur de transition les traduit en lignes au format hérité, pour que la facturation et le reporting continuent de fonctionner sans changement. Un rapprochement quotidien compare les nombres et les totaux monétaires entre les deux stockages. Ces modules passent ensuite aux événements ou à l’API des commandes au cours des trimestres suivants.
6. Migrer l’historique et retirerTrois années de commandes historiques sont copiées par lots à débit limité et rapprochées par nombres et sommes de contrôle. Quand les journaux d’audit de la base de données ne montrent aucune lecture des anciennes tables de commandes pendant 30 jours, l’équipe supprime l’adaptateur, le pipeline de capture des données modifiées et les anciennes règles de routage, puis supprime du monolithe le code et les tables des commandes. La migration n’est terminée qu’à ce moment-là.

Chaque étape livre quelque chose d’utilisable ou supprime un risque, et chaque étape sauf la dernière dispose d’un retour arrière peu coûteux.

Le système de référence des commandes est sans ambiguïté à chaque étape, et les données circulent dans un seul sens à la fois.

L’architecture de transition est planifiée, mesurée et délibérément supprimée ; la réussite se mesure à ce qui a été éteint.

Vérifiez votre compréhension

Pourquoi une réécriture big bang tend-elle à perdre des comportements dont les utilisateurs dépendent ?

Parce que des années de corrections de bugs et de règles métier n’existent que dans l’ancien code et dans les habitudes des utilisateurs ; personne ne peut toutes les énumérer, si bien qu’une réécriture à partir d’une spécification en oublie certaines.

Qu’apporte la façade de routage à une migration strangler fig ?

Elle permet de déplacer le trafic tranche par tranche, et de le ramener si quelque chose tourne mal, sans modifier le système hérité à chaque étape.

Qu’est-ce que l’architecture de transition, et pourquoi suivre sa suppression ?

Du code temporaire, comme des règles de routage, des adaptateurs et des tâches de synchronisation, qui permet à l’ancien et au nouveau de coexister ; suivre sa suppression protège contre une migration qui cale, avec les deux systèmes en fonctionnement indéfiniment.

Quand le rachat peut-il être un meilleur choix que la refactorisation ?

Quand la capacité est générique plutôt que différenciante et qu’un bon produit existe : la reconstruire consacrerait des efforts là où l’entreprise ne se distingue pas de la concurrence.

Suivant · 11.7LeadershipComment les architectes dirigent sans autorité formelle : bâtir la confiance, faciliter des décisions par consentement ou par consensus, gérer les conflits et les jeux politiques, parler aux dirigeants, emprunter l’ascenseur de l’architecte et faire grandir d’autres architectes.Continuer →

Sources et lectures complémentaires

Une question sur cette section ? ArchitectBot, l’assistant d’étude IA du cours, répond d’abord à partir du cours.

Demander : « Notre système central est un monolithe de 15 ans que seules deux personnes comprennent. Comment décider s’il faut le réécrire, et sinon, par où commencer une migration incrémentale ? »