Be Software Architecture Wise
Be Wise Apps

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

Section 5.5 sur 5.8 · Intermédiaire · 24 min de lecture

Architectures micronoyau et à plug-ins

Leçon vidéo

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

Beaucoup de produits à succès sont un petit noyau que d’autres étendent. Dans cette leçon, vous apprendrez comment fonctionne le style micronoyau, ou à plug-ins, dans quels cas il convient, et comment garder ses contrats sains au fil des années.

Pensez à une console de jeux. La console elle-même fait quelques tâches générales : elle démarre, affiche un menu, lit les manettes et dessine les images. Chaque jeu est une cartouche qui s’insère par un connecteur standard. Vous pouvez ajouter de nouveaux jeux pendant des années sans ouvrir la console, et un jeu mal conçu ne peut pas en modifier le câblage. Une architecture micronoyau construit le logiciel de la même façon.

Voici sa forme. Le système central contient la logique minimale et générale : le flux de travail principal et les points où se branchent les plug-ins. Dans l’étape de paiement d’une plateforme de livraison de repas, cela consiste à calculer le prix d’un panier, encaisser le paiement et confirmer la commande. À l’intérieur du noyau, un registre enregistre quels plug-ins existent, leurs versions et le contrat que chacun implémente. Il permet au noyau de demander les règles fiscales de l’Allemagne sans savoir quelle classe les fournit. Les plug-ins ne se connectent que par des contrats que publie le noyau. D’un côté, les règles fiscales du Royaume-Uni, de l’Allemagne et des États-Unis. De l’autre, les moyens de paiement : cartes, portefeuilles électroniques et paiement en espèces à la livraison. Deux règles font tenir l’ensemble. Les plug-ins ne s’appellent jamais entre eux, et le noyau ne nomme jamais un plug-in particulier.

Ce style convient aux produits dont le flux de travail est stable mais dont certaines étapes ont de nombreuses variantes. Les règles fiscales et de paie par pays, les règles d’assurance par juridiction, les moyens de paiement, les importateurs de fichiers. Il convient aussi aux plateformes qui veulent un écosystème d’extensions tierces. Des outils de développement comme Eclipse et Visual Studio Code, ainsi que les navigateurs web, sont construits ainsi. Visual Studio Code va jusqu’à exécuter les extensions dans un processus séparé, afin qu’une extension défaillante ne puisse pas figer l’éditeur.

Concevons-en un. La plateforme de livraison de repas se lance en Allemagne et aux États-Unis après le Royaume-Uni, et son étape de paiement est déjà pleine de branches pour chaque pays. D’abord, nommez le point de variation. Seule la taxe sur un panier dont le prix est établi varie selon le pays. La tarification, le paiement et les reçus restent dans le noyau, ce qui garde le contrat petit. Ensuite, décidez de ce qui traverse le contrat. Les lignes du panier entrent, chacune avec une catégorie neutre, comme plat chaud ou frais de livraison. Le total de la taxe et une ventilation étiquetée en sortent. Aucune commande, aucun client ni aucune session de base de données n’est transmis, si bien que les plug-ins ne peuvent pas dépendre des éléments internes du noyau. Le contrat devient un petit paquet, versionné séparément. Chaque plug-in déclare la version dont il a besoin, et le registre vérifie au démarrage, si bien qu’une incompatibilité apparaît au déploiement, et non sous la forme d’une taxe erronée sur un reçu. Si aucun plug-in n’existe pour un pays, l’étape de paiement refuse la commande plutôt que d’appliquer une taxe nulle. Enfin, une suite de tests de contrat partagée s’exécute contre chaque plug-in. Ajouter un quatrième pays représente désormais un nouveau paquet et ses tests, pas une modification de l’étape de paiement.

Voici le tout à l’exécution. L’étape de paiement demande au registre le plug-in enregistré pour l’Allemagne, et le récupère. Puis elle appelle ce plug-in par le contrat. Remarquez le plug-in des États-Unis. Il a été construit pour une ancienne version majeure du contrat, donc le registre l’a ignoré au démarrage, avec un message de journal, au lieu de faire planter l’étape de paiement.

Trois choses gardent une architecture à plug-ins en bonne santé. Premièrement, l’isolation. Les plug-ins internes au processus sont rapides, mais un seul plug-in avec une fuite mémoire affecte tout. Les règles fiscales de votre propre équipe peuvent s’exécuter dans le processus ; du code arbitraire provenant d’une place de marché publique ne le devrait pas. Deuxièmement, le versionnage. Privilégiez les changements additifs, comme de nouveaux champs facultatifs, pour que les anciens plug-ins continuent de fonctionner. Troisièmement, la loi de Hyrum : avec suffisamment d’utilisateurs, chaque comportement observable d’une interface finira par être utilisé par quelqu’un. Gardez donc le contrat petit, et traitez tout ce que les plug-ins peuvent voir comme en faisant partie. Et ne construisez pas de points d’extension pour des besoins imaginés. Attendez d’avoir construit deux ou trois fois le même type de variation, puis extrayez le contrat à partir des cas réels.

La section complète explique en quoi cela diffère d’un micronoyau de système d’exploitation, liste les anti-patrons courants et montre le contrat et le registre dans le code. Ensuite, à la section cinq point six : architecture hexagonale, en oignon et Clean Architecture. À très vite.

Ce qu’il faut savoir

Définition et origine

Une architecture micronoyau, aussi appelée architecture à plug-ins, divise une application en deux sortes de parties : un système central qui contient les fonctionnalités minimales et générales nécessaires pour fonctionner, et des plug-ins qui ajoutent ou modifient des comportements sans que le noyau soit modifié. Les plug-ins ne se connectent au noyau que par des contrats publiés, et le noyau les trouve au moyen d’un registre. Mark Richards et Neal Ford le cataloguent parmi les styles d’architecture classiques dans Fundamentals of Software Architecture, et il correspond à la description d’un style architectural donnée à la section 5.1 : un ensemble nommé de contraintes portant sur les éléments (noyau, plug-ins) et les connecteurs (contrats) d’un système.

L’idée est née dans les logiciels produits : des outils installés par de nombreux clients, chacun voulant quelque chose de légèrement différent. Plutôt que de maintenir une version distincte pour chaque client, l’éditeur livre un seul noyau et laisse les clients, les partenaires ou ses propres équipes ajouter des plug-ins. Les environnements de développement intégrés, les navigateurs web, les éditeurs graphiques et les logiciels de comptabilité fonctionnent tous ainsi. Le nom est emprunté aux systèmes d’exploitation, où un micronoyau est un très petit noyau autour duquel tout le reste s’exécute à l’extérieur ; les deux idées sont liées mais ne doivent pas être confondues, comme expliqué plus loin.

Une application micronoyau forme normalement une seule unité de déploiement ; elle appartient donc aux styles monolithiques de ce chapitre : une seule unité de déploiement, un seul processus, un seul ensemble de préoccupations d’exploitation. Les plug-ins peuvent être installés séparément, mais à l’exécution ils vivent à l’intérieur du noyau, ou juste à côté.

La forme d’un système micronoyau

Une architecture micronoyau : un système central doté d’un registre de plug-ins, et des plug-ins qui ne se connectent au noyau que par des contrats publiésUne application déployable (plug-ins chargés au démarrage ou à l’exécution)Noyaulogique générale minimaleRegistre plug-insid, version, contratcontratcontratPlug-in ATaxes R.-U.Plug-in BTaxes AllemagnePlug-in CTaxe vente USPlug-in DPaiement cartePlug-in EE-portefeuillePlug-in FEspèces livraisonLes plug-ins ne s’appellent jamais ; le noyau ne nomme aucun plug-inFlèches : chaque plug-in implémente un contrat publié par le noyau
Le système central possède le flux de travail général et un registre ; chaque plug-in implémente un contrat publié par le noyau, et les plug-ins ne dépendent jamais les uns des autres.

Fonctionnement : noyau, plug-ins, registre et contrats

Le système central contient ce dont chaque installation a besoin : le flux de travail principal, des services partagés comme la journalisation ou la configuration, et les points d’extension où se branchent les plug-ins. Richards et Ford le décrivent comme le chemin nominal à travers l’application, avec peu ou pas de traitement personnalisé ; dans l’étape de paiement d’une plateforme de livraison de repas, cela consiste à calculer le prix d’un panier, encaisser le paiement et confirmer la commande. Un bon noyau est volontairement ennuyeux. Chaque cas particulier qui varie selon le client, le pays ou la gamme de produits est un candidat à en sortir.

Les plug-ins sont des modules indépendants et autonomes qui contiennent un traitement spécialisé. Ils ne doivent dépendre que du contrat, ni les uns des autres ni des classes internes du noyau. Quand un plug-in a besoin de quelque chose qu’un autre fournit, ce besoin doit être satisfait par le noyau (sous la forme d’un service qu’il expose par le contrat), ou bien les deux plug-ins doivent être fusionnés ; les dépendances de plug-in à plug-in transforment discrètement une étoile bien nette en enchevêtrement.

Le registre enregistre quels plug-ins sont disponibles, comment chacun s’appelle, quelle est sa version, quel contrat il implémente et comment l’atteindre. Il peut être aussi simple qu’un dictionnaire rempli au démarrage, un répertoire que le noyau parcourt à la recherche de manifestes, des entrées dans les métadonnées d’un paquet (comme avec les points d’entrée Python), une table de base de données ou un service distant qui liste des plug-ins déployés ailleurs. C’est le registre qui permet au noyau de dire « donne-moi les règles fiscales de l’Allemagne » sans savoir qu’une classe appelée GermanTax existe.

Les contrats définissent ce qu’un plug-in doit fournir et ce sur quoi il peut compter : une interface avec des signatures de méthodes, les structures de données qui la traversent, le comportement en cas d’erreur et une version. Dans Eclipse, un contrat est un point d’extension déclaré dans le manifeste d’un plug-in avec un schéma ; dans Visual Studio Code, c’est l’API d’extension plus les points de contribution listés dans le package.json d’une extension ; dans votre propre code, c’est généralement une interface (ou un Protocol Python) placée dans un petit paquet à part. Lorsque le plug-in vient d’une autre organisation et ne peut pas suivre votre contrat, un adaptateur fait la traduction entre les deux, exactement comme dans l’architecture hexagonale.

Les plug-ins peuvent être connectés de trois façons. Les appels point à point, dans le processus sont la norme : le noyau appelle une méthode sur l’objet plug-in. Les plug-ins en processus séparé s’exécutent dans leur propre processus et communiquent avec le noyau par des messages interprocessus, ce qui protège le noyau des plantages. Les plug-ins distants se trouvent derrière une interface HTTP ou de messagerie ; cela permet des plug-ins écrits dans d’autres langages ou détenus par d’autres équipes, mais ajoute de la latence réseau et des modes de défaillance, et le système commence à ressembler aux styles distribués du chapitre 6.

Exemples réels

Eclipse. L’IDE Eclipse est le cas d’école. Son noyau n’est guère plus qu’un plan de travail et un runtime de plug-ins (bâti sur Equinox, une implémentation de la spécification du framework OSGi). Presque tout ce que voit un développeur, des outils Java à l’intégration de la gestion de versions, est un plug-in. Chaque plug-in possède un manifeste qui déclare ce dont il a besoin, quels points d’extension il offre aux autres et à quelles extensions il se branche. Les plug-ins sont activés de façon paresseuse, seulement quand quelque chose en a réellement besoin, afin que des centaines de plug-ins installés ne ralentissent pas le démarrage.

Visual Studio Code. Les extensions déclarent des événements d’activation (ouvrir un fichier Markdown, exécuter une commande) afin de ne se charger qu’en cas de besoin, et apportent des commandes, des langages, des thèmes et des vues par leur manifeste. Les extensions s’exécutent dans un processus hôte d’extensions séparé, si bien qu’une extension défaillante ne peut pas figer l’interface utilisateur de l’éditeur. C’est un bon exemple de choix de l’isolation par processus pour un écosystème de plug-ins qui compte des milliers d’auteurs tiers.

Navigateurs web. Les extensions de navigateur utilisent un ensemble standard d’API d’extension et doivent déclarer dans un manifeste les permissions dont elles ont besoin (quels sites elles peuvent lire, si elles peuvent voir les onglets ou les téléchargements). Le navigateur fait respecter ces permissions, car on ne peut pas, par défaut, faire confiance aux auteurs d’extensions.

Moteurs de règles métier. Le système de gestion des sinistres d’un assureur peut garder le flux de traitement des sinistres dans le noyau et placer les règles de chaque juridiction (ce qui est couvert, quels documents sont nécessaires, délais légaux) dans des plug-ins. Les moteurs fiscaux, les systèmes de paie et les plateformes de commerce en ligne dotées de plug-ins de moyens de paiement suivent le même modèle : un processus stable, avec des règles ou des prestataires qui varient selon le pays, le produit ou le client.

Outils de développement. Le framework de test pytest est construit autour de la bibliothèque de plug-ins pluggy ; nombre de ses propres fonctionnalités sont implémentées sous forme de plug-ins utilisant les mêmes hooks que ceux qu’utilisent les tiers. Les outils de build, les linters et les générateurs de sites statiques font couramment de même.

Micronoyau applicatif ou micronoyau de système d’exploitation

AspectMicronoyau de système d’exploitationArchitecture micronoyau applicative (à plug-ins)
Ce qui est minimalLe noyau : espaces d’adressage, threads, ordonnancement et communication interprocessusLe système central : le flux de travail général d’une application
Ce qui est hors du noyauPilotes de périphériques, systèmes de fichiers et piles réseau, exécutés comme serveurs séparés en espace utilisateurLes comportements métier ou fonctionnalités variables, comme les règles fiscales, les moyens de paiement ou les langages d’un éditeur
Objectifs principauxFiabilité, sécurité et une petite base informatique de confianceExtensibilité, personnalisation et un écosystème de tiers
Communication entre les partiesPassage de messages entre processus isolésGénéralement des appels dans le processus via une interface ; parfois un processus séparé ou un appel distant
ExemplesMach, la famille L4 (dont seL4, formellement vérifié), QNX, MINIX 3Eclipse, Visual Studio Code, navigateurs web, pytest, moteurs de règles d’assurance et fiscales

Isolation, versionnage et compatibilité des plug-ins

Trois questions décident si une architecture à plug-ins reste saine au fil des années : à quel point les plug-ins sont isolés, comment le contrat évolue, et comment l’incompatibilité est détectée.

Isolation. Les plug-ins internes au processus partagent la mémoire, les threads et les permissions du noyau. C’est rapide et simple, mais un seul plug-in avec une boucle infinie, une fuite mémoire ou une faille de sécurité affecte tout. Les options, par ordre croissant d’isolation et de coût, sont : dans le processus avec des appels défensifs (délais d’expiration, capture des exceptions plug-in par plug-in, limites de ressources) ; un processus ou un worker séparé par plug-in ou par groupe de plug-ins ; une sandbox (bac à sable), comme un runtime WebAssembly ou un interpréteur restreint, qui limite ce que le code du plug-in peut toucher ; et un service distant. Choisissez en vous demandant qui écrit les plug-ins. Les règles fiscales de votre propre équipe peuvent s’exécuter dans le processus ; du code arbitraire provenant d’une place de marché publique ne le devrait pas.

Versionnage. Le contrat est une API, et les règles de la section 0.6 sur les changements cassants s’appliquent. Versionnez le contrat séparément du noyau, généralement avec le versionnage sémantique : une nouvelle version majeure pour les changements cassants, une version mineure pour les ajouts rétrocompatibles. Laissez chaque plug-in déclarer pour quelle version du contrat il a été construit, et laissez le noyau déclarer quelles versions il prend en charge. Les changements additifs (nouveaux champs facultatifs, nouveaux hooks facultatifs) permettent aux anciens plug-ins de continuer à fonctionner ; quand un changement cassant est inévitable, prenez en charge l’ancienne et la nouvelle version majeure côte à côte pendant une période de dépréciation annoncée.

Vérifications de compatibilité. Vérifiez la compatibilité au chargement des plug-ins, pas au moment où un client les déclenche pour la première fois. Un registre qui refuse un plug-in incompatible au démarrage, avec un message de journal clair, transforme un bogue à l’exécution en échec de déploiement. L’observation de Hyrum Wright, connue sous le nom de loi de Hyrum, mérite d’être retenue ici : avec suffisamment d’utilisateurs, chaque comportement observable d’une interface finira par être utilisé par quelqu’un, quoi que dise la documentation. Gardez le contrat petit, gardez les éléments internes du noyau hors d’atteinte (Eclipse marque pour cette raison les paquets hors API comme internal), et traitez tout ce qui est visible par les plug-ins comme faisant partie du contrat.

Caractéristiques du style micronoyau

CaractéristiqueÉvaluationJustification
Extensibilité et modifiabilité (dans les directions prévues)ÉlevéUn nouveau comportement arrive sous la forme d’un nouveau plug-in ; le noyau et les autres plug-ins ne changent pas.
SimplicitéMoyenL’idée est simple, mais les contrats, le registre, le chargement et les vérifications de version constituent une vraie mécanique.
TestabilitéMoyen à élevéChaque plug-in peut être testé seul par rapport au contrat ; le noyau peut être testé avec de faux plug-ins. Les combinaisons doivent tout de même être testées.
DéployabilitéMoyenOn peut souvent ajouter des plug-ins sans redéployer le noyau ; un changement de contrat impose des mises en production coordonnées.
PerformancesMoyen à élevéLes plug-ins internes au processus sont de simples appels de fonction ; l’isolation par processus ou par sandbox ajoute un surcoût.
Scalabilité et élasticitéBasC’est généralement une seule unité de déploiement, qui passe donc à l’échelle d’un bloc, comme tout monolithe.
Tolérance aux pannesBas à moyenUn plug-in interne au processus peut planter ou ralentir l’hôte ; l’isolation par processus améliore ce point, mais a un coût.
Coût globalÉlevé (peu coûteux)Une seule base de code et un seul runtime à exploiter ; le coût principal est la conception et l’évolution des contrats.

Quand l’utiliser, et quand l’éviter

Bons cas d’usage. Les logiciels produits que de nombreux clients installent et personnalisent. Les systèmes au flux de travail stable mais avec de nombreuses variantes d’une étape : fiscalité et paie par pays, règles d’assurance par juridiction, moyens de paiement, importateurs de formats de fichiers, transporteurs. Les plateformes qui veulent un écosystème de tiers. Les systèmes où un régulateur ou un client modifie souvent les règles et où vous voulez que ces modifications soient de petites mises en production testées séparément. Le style fonctionne aussi au sein d’un système plus vaste : un service d’une architecture distribuée peut lui-même être un micronoyau.

Mauvais cas d’usage. Les systèmes dont vous ne savez pas encore ce qui varie. Des points d’extension conçus à l’avance pour des besoins imaginés relèvent de la généralité spéculative : ils ajoutent de la mécanique, ralentissent l’équipe et se trouvent généralement au mauvais endroit quand le vrai besoin arrive (voir la discussion sur la réversibilité à la section 1.6). Les systèmes qui exigent une très grande scalabilité ou une isolation des pannes, puisque l’ensemble passe à l’échelle et tombe en panne comme une seule unité. Les systèmes où les plug-ins devraient se coordonner étroitement entre eux, ce qui annule le principal avantage du style.

Une règle pratique : attendez d’avoir construit deux ou trois fois le même type de variation, puis extrayez le contrat à partir des cas réels. Un contrat dérivé de trois implémentations fiscales qui fonctionnent sera bien meilleur qu’un contrat deviné avant la première.

Exemple commenté

Exemple commenté : concevoir un contrat de plug-in pour des règles fiscales propres à chaque pays

La plateforme de livraison de repas utilisée tout au long de ce cours se lance en Allemagne et aux États-Unis après le Royaume-Uni. Chaque pays taxe différemment les plats, l’alcool et les frais de livraison, et la taxe sur les ventes américaine varie aussi selon l’État et la ville. Le code de l’étape de paiement contient déjà des branches if country == "GB". L’architecte propose un micronoyau : l’étape de paiement est le noyau, et les règles de chaque pays sont un plug-in. Voici les décisions, dans l’ordre où elles ont été prises.

1. Nommer le point de variationSeul le calcul de la taxe sur un panier dont le prix est établi varie selon le pays. La tarification, les remises, le paiement et les reçus restent dans le noyau. Un point de variation étroit garde le contrat petit.
2. Décider de ce qui traverse le contratEntrées : les lignes du panier avec une catégorie neutre (hot-food, alcohol, delivery-fee), des montants hors taxe en Decimal et une région facultative. Sortie : le total de la taxe et une ventilation étiquetée pour le reçu. Aucun objet du noyau (commandes, clients, sessions de base de données) n’est transmis, si bien que les plug-ins ne peuvent pas dépendre des éléments internes du noyau.
3. Faire du contrat un paquet séparétax_contract ne contient que des classes de données et le protocole TaxRules, versionné selon le versionnage sémantique. Le noyau et les plug-ins en dépendent tous deux ; aucun ne dépend de l’autre.
4. Définir la compatibilitéUn plug-in déclare la version du contrat dont il a besoin, par exemple (2, 1). Le noyau l’accepte si la version majeure correspond et si la version mineure du noyau est au moins aussi élevée. Ajouter un champ facultatif est un changement mineur ; renommer ou supprimer un champ est un changement majeur.
5. Choisir le mode de découverteLes plug-ins sont des paquets Python ordinaires qui s’enregistrent sous le groupe de points d’entrée fooddelivery.tax_rules. Installer un paquet suffit ; aucun code du noyau ne liste les pays.
6. Décider du comportement en cas d’échecSi aucun plug-in n’est enregistré pour un pays, l’étape de paiement refuse la commande avec une erreur claire plutôt que d’appliquer une taxe nulle. Un plug-in qui lève une exception ne fait échouer que ce paiement-là, et l’erreur est signalée avec le nom et la version du plug-in.
7. Décider de l’isolationLes plug-ins sont écrits par les propres équipes pays de l’entreprise et sont dignes de confiance ; ils s’exécutent donc dans le processus, pour la rapidité. Si des éditeurs fiscaux externes étaient autorisés à fournir des plug-ins, la conception les placerait derrière un appel distant avec un délai d’expiration.
8. Spécifier les testsUne suite de tests de contrat partagée (arrondis, paniers vides, lignes de remboursement négatives, catégories inconnues) s’exécute contre chaque plug-in dans son propre pipeline. Chaque équipe pays y ajoute ses propres cas, tirés des instructions fiscales publiées.

Le noyau ne change plus quand un pays est ajouté : le lancement d’un quatrième pays représente donc un nouveau paquet et ses tests, pas une modification de l’étape de paiement.

La décision la plus difficile est l’étape 2. Une liste de catégories trop grossière oblige les plug-ins à deviner ; une liste trop détaillée couple le contrat au droit d’un seul pays. Attendez-vous à la réviser, et planifiez alors la migration vers une nouvelle version majeure.

Le registre rejette les plug-ins incompatibles au démarrage : une incompatibilité de version apparaît donc lors d’une vérification de déploiement, et non sous la forme d’une taxe erronée sur le reçu d’un client.

# package: tax_contract  (version 2.1.0)
from dataclasses import dataclass
from decimal import Decimal
from typing import Protocol

CONTRACT_VERSION = (2, 1)

@dataclass(frozen=True)
class TaxLine:
    sku: str
    category: str      # "hot-food", "cold-food", "alcohol", "delivery-fee"
    net: Decimal       # never float for money

@dataclass(frozen=True)
class TaxResult:
    total_tax: Decimal
    breakdown: dict[str, Decimal]   # label shown on the receipt

class TaxRules(Protocol):
    country: str                    # ISO 3166-1 alpha-2, e.g. "DE"
    requires_contract: tuple[int, int]
    def calculate(self, lines: list[TaxLine], region: str | None) -> TaxResult: ...
Le paquet du contrat, versionné et publié séparément du noyau comme des plug-ins.
from importlib.metadata import entry_points
from tax_contract import CONTRACT_VERSION

class UnsupportedCountry(Exception): pass

class TaxRegistry:
    def __init__(self, log):
        self._by_country = {}
        for ep in entry_points(group="fooddelivery.tax_rules"):
            plugin = ep.load()()
            major, minor = plugin.requires_contract
            if major != CONTRACT_VERSION[0] or minor > CONTRACT_VERSION[1]:
                log.warning("skipping %s: needs contract %s", ep.name, plugin.requires_contract)
                continue
            self._by_country[plugin.country] = plugin

    def for_country(self, country):
        if country not in self._by_country:
            raise UnsupportedCountry(country)   # fail loudly, never guess a rate
        return self._by_country[country]
Le registre dans le noyau : il découvre les plug-ins installés, vérifie la compatibilité des contrats et les recherche par pays.

Les plug-ins fiscaux à l’exécution

Exemple commenté : l’étape de paiement demande au registre fiscal le plug-in enregistré pour le pays du client et l’appelle par le contrat TaxRulesPaiementcas du noyauTaxRegistrypays → plug-in1 for("DE")2 plug-inTaxRulescontrat v2.13 calculate(lines)gb_tax 2.0.4exige v2.0de_tax 1.3.0exige v2.1us_tax 3.1.0exige v1.4ignoré : versionmajeure erronéeChargement : au démarrage, le registre lit les paquets installés (points d’entrée), vérifiela version de contrat requise par chaque plug-in et l’enregistre par pays ; les plug-insincompatibles sont journalisés et ignorés sans planter.enregistré parcode pays
L’étape de paiement demande au registre le plug-in enregistré pour un pays et l’appelle par le contrat ; un plug-in construit pour une version de contrat incompatible est ignoré au démarrage.

Faire respecter le style dans le code

Une architecture à plug-ins ne le reste que si ses règles sont vérifiées automatiquement. La structure qui fonctionne dans la plupart des langages comporte trois sortes de paquets : un petit paquet de contrat ne contenant que des interfaces et des types de données ; le noyau, qui dépend du contrat ; et un paquet par plug-in, chacun dépendant du contrat et de rien d’autre parmi votre code.

Faites ensuite respecter les règles de dépendance avec des outils du type présenté à la section 3.4 : en Python, import-linter peut interdire au noyau d’importer un paquet de plug-in et interdire aux plug-ins de s’importer entre eux ou d’importer le noyau ; en Java, des tests ArchUnit ou des systèmes de modules (JPMS, OSGi) peuvent faire de même. Faites échouer le build quand une règle est enfreinte.

Ajoutez une suite de tests de contrat : un ensemble de tests écrits une fois pour toutes par rapport au contrat et exécutés contre chaque plug-in dans le pipeline propre à ce plug-in. Elle vérifie le comportement que promet le contrat (arrondis, entrée vide, types d’erreur), et non le droit fiscal de chaque pays, que couvrent les tests propres au plug-in. Enfin, ajoutez une vérification au démarrage ou un test de fumée (smoke test) de déploiement qui charge chaque plug-in installé et vérifie sa version de contrat, afin que les incompatibilités soient détectées avant que les utilisateurs ne les découvrent.

Questions fréquentes

Un micronoyau n’est-il que le patron Stratégie ?

Au niveau du code, le patron Stratégie est le même mécanisme : choisir à l’exécution une implémentation d’une interface. Le micronoyau en est la version architecturale : les plug-ins sont construits et versionnés séparément, et souvent installés séparément, un registre les découvre, et le contrat est une API publiée avec des règles de compatibilité.

Les plug-ins peuvent-ils avoir leurs propres données ?

Oui, et c’est souvent souhaitable, mais traitez-les comme les données détenues par les modules d’un monolithe modulaire : les tables ou les fichiers d’un plug-in lui appartiennent, et le noyau n’y accède que par le contrat.

La configuration doit-elle remplacer les plug-ins ?

Pour de petites différences relevant des données (un taux, un seuil, un libellé), oui : la configuration est moins coûteuse et plus sûre. Les plug-ins servent aux différences qui exigent de la logique. Beaucoup de systèmes utilisent les deux : un plug-in par pays, plus une configuration pour les taux en vigueur dans chaque pays.

Vérifiez votre compréhension

Quels sont les trois éléments qu’une architecture à plug-ins doit définir en plus du noyau lui-même ?

Les plug-ins, les contrats qu’ils implémentent, et un registre qui permet au noyau de découvrir et de rechercher les plug-ins sans les nommer dans le code.

Pourquoi les plug-ins ne doivent-ils pas dépendre les uns des autres ?

Parce qu’ils ne pourraient alors plus être installés, retirés, testés et versionnés indépendamment, ce qui est le principal avantage du style ; les besoins partagés relèvent du noyau ou du contrat.

Pourquoi Visual Studio Code exécute-t-il les extensions dans un processus séparé ?

Pour qu’une extension lente ou qui plante ne puisse pas figer ni faire planter l’interface utilisateur de l’éditeur : il sacrifie un peu de performances et de simplicité en échange d’une isolation vis-à-vis de milliers d’auteurs tiers.

Quand la configuration est-elle préférable à un plug-in ?

Quand la différence entre les variantes relève des données, comme un taux, une limite ou un libellé, plutôt que de la logique. La configuration est plus simple et plus sûre à modifier.

Suivant · 5.6Hexagonale et Clean ArchitecturePorts et adaptateurs, architecture en oignon et Clean Architecture : garder la logique métier au centre avec des dépendances orientées vers l’intérieur, ports pilotes et ports pilotés, un exemple en Python avec un véritable adaptateur et une doublure de test, et les coûts.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 : « Nous vendons une application d’intervention sur le terrain à 40 clients, et chacun veut des règles personnalisées pour la planification des interventions. Devons-nous construire une architecture à plug-ins, ou la configuration suffit-elle ? »