Les équipes devraient retarder l’adoption des microservices jusqu’à ce qu’un service ralentisse la livraison
#Backend

Les équipes devraient retarder l’adoption des microservices jusqu’à ce qu’un service ralentisse la livraison

Marta Kowalska
Marta Kowalska
7 min read

Rust et Go peuvent alimenter des services backend rapides, mais les équipes tirent davantage parti d’une propriété claire, d’API stables et de découpages mesurés que d’une prolifération de services.

Image mise en avant

Une équipe devrait découper un backend en microservices lorsqu’une seule base de code bloque la vitesse de déploiement, la montée en charge ou l’autonomie. Rust et Go peuvent aider une fois ce cap atteint, mais le choix du langage ne peut pas corriger des frontières faibles, une propriété des données floue ou un réseau rempli d’API trop bavardes.

Les microservices ajoutent des processus, des sauts réseau, des chemins de déploiement, des besoins d’observabilité et des modes de défaillance. Un service unique maintient ces coûts bas pendant qu’une équipe produit apprend son domaine. Vous obtenez une seule unité de déploiement, un seul modèle de données, une seule frontière transactionnelle et un seul endroit où inspecter les logs.

Un petit backend a souvent davantage besoin de cette simplicité que d’une mise à l’échelle indépendante. Si une seule équipe possède le produit, un monolithe modulaire peut offrir aux développeurs des frontières de packages propres sans forcer chaque fonctionnalité à passer par HTTP, les files d’attente, la découverte de services et le traçage distribué.

La séparation commence à avoir du sens lorsqu’une frontière de service correspond à une frontière métier. Les paiements, l’identité, la recherche, le traitement média et l’analytique nécessitent souvent des règles de mise à l’échelle différentes. Une API de paiement peut exiger une faible latence et une cohérence stricte. Un pipeline de reporting peut tolérer du retard et privilégier le débit.

Les équipes devraient commencer par les données. Un microservice possède ses données, ses changements de schéma et ses invariants. Si deux services écrivent dans les mêmes lignes, les développeurs ont construit un monolithe distribué avec des points de défaillance supplémentaires. Si un service peut modifier son schéma sans coordonner un déploiement à l’échelle de l’entreprise, la frontière a de la valeur.

La cohérence façonne la conception. Un monolithe peut s’appuyer sur les transactions de base de données. Un système à microservices doit faire des choix explicites : appels synchrones pour les lectures qui ont besoin de données fraîches, événements pour les workflows qui peuvent prendre du retard, et clés d’idempotence pour les commandes que les clients peuvent retenter.

Par exemple, un service de commande peut accepter une demande de paiement, enregistrer la commande et publier un événement. Un service d’exécution peut consommer cet événement et réserver le stock. Si l’étape d’exécution échoue, le service de commande a besoin d’une machine à états claire, comme en attente, confirmée, échouée ou annulée. Les développeurs doivent concevoir ces états à l’avance, car aucune transaction partagée ne viendra sauver le workflow.

La conception des API porte la même charge. Les équipes ont besoin de contrats stables, de charges utiles versionnées, de codes d’erreur clairs et de délais d’attente alignés sur le parcours produit. Une passerelle d’API Go peut router le trafic et gérer la concurrence avec des goroutines. Un worker Rust peut traiter les chemins de données chauds là où la sécurité mémoire et le coût CPU comptent. Ces choix fonctionnent mieux après que l’équipe a défini la frontière.

Image d’article Google

Go convient à beaucoup de services d’entrée de gamme, car les développeurs peuvent livrer des serveurs HTTP lisibles avec la bibliothèque standard et des routeurs courants. Le langage offre des builds rapides, des artefacts de déploiement simples et un modèle de concurrence qui correspond bien à la gestion des requêtes. La documentation officielle de Go donne aux équipes suffisamment d’outillage de base pour construire des services réseau sans une pile de frameworks massive.

Rust convient aux services qui exigent un contrôle étroit sur la mémoire, les data races et la latence. Les équipes le choisissent souvent pour des serveurs de cache, des parseurs, des processeurs de flux et d’autres composants situés sur le chemin critique. Le projet Rust offre aux développeurs des règles de propriété et d’emprunt qui détectent des classes de bugs mémoire avant que le code n’atteigne la production.

Un serveur d’API JSON Rust peut utiliser un parsing sans copie et l’E/S asynchrone pour réduire la pression sur les allocations. Cela peut aider un endpoint qui traite de grosses charges utiles ou un volume élevé de requêtes. Le même choix peut ralentir une équipe qui modifie encore ses formes de requêtes chaque semaine. Rust demande aux développeurs de modéliser la propriété avec soin. Ce coût devient rentable lorsque le domaine s’est stabilisé.

Une passerelle Go peut se placer devant plusieurs services et appliquer des limites de requêtes, l’authentification et des règles de routage. Ce schéma aide lorsque les clients ont besoin d’un point d’entrée unique et que les équipes backend ont besoin de marge pour faire évoluer les services internes. Il peut devenir problématique si la passerelle se transforme en une seconde application contenant de la logique métier copiée depuis chaque service.

Le schéma d’API le plus solide garde les commandes étroites et les lectures claires. Un endpoint de commande comme POST /orders devrait accepter une seule intention et renvoyer un résultat durable. Un endpoint de lecture comme GET /orders/{id} devrait exposer l’état dont les clients ont besoin sans leur faire appeler cinq services. Si une page a besoin d’une vue combinée, un backend-for-frontend ou un service de requête peut l’assembler.

Les événements exigent la même discipline. Une équipe devrait publier des faits survenus, comme OrderCreated ou PaymentCaptured, et conserver des schémas d’événements stables. Les consommateurs devraient gérer les doublons et les livraisons hors ordre. Les producteurs devraient utiliser le pattern outbox pour qu’une écriture en base et une publication d’événement ne divergent pas.

La scalabilité a elle aussi plusieurs niveaux. Un service unique peut monter en charge grâce au cache, aux réplicas de lecture, aux workers alimentés par file d’attente et à de meilleurs index. Ces actions coûtent moins cher qu’un découpage prématuré du domaine. Une équipe devrait épuiser les options peu coûteuses avant d’accepter la défaillance distribuée comme taxe permanente.

Les microservices aident lorsque les équipes ont besoin d’un rythme de livraison séparé. Une équipe de recherche peut déployer des changements de classement sans toucher au paiement. Une équipe plateforme peut corriger l’identité sans demander aux équipes produit de reconstruire leurs applications. La propriété devient visible dans le code, les tableaux de bord, les alertes et la réponse aux incidents.

Cette propriété a un revers. Un propriétaire de service assume désormais l’astreinte, la planification de capacité, les migrations de schéma, le support d’API et le travail de dépréciation. Si aucune équipe ne peut assumer ces responsabilités, la frontière du service se dégradera. Les chaînes d’appels s’allongeront, les délais d’attente s’empileront, et les développeurs débogueront la production en lisant les logs de cinq déploiements.

L’observabilité devrait précéder la séparation. Les développeurs ont besoin d’identifiants de requête, de logs structurés, de métriques, de traces et d’objectifs de niveau de service avant d’ajouter des frontières réseau. Sans ces outils, une simple dépendance lente peut transformer un incident courant en devinette.

pic

Une voie pratique commence par un monolithe modulaire. Placez les paiements, les comptes, les commandes et les notifications dans des modules séparés. Interdisez l’accès direct à la base de données entre les frontières de modules. Utilisez des interfaces qui ressemblent à de futures API. Ajoutez des tests autour des contrats. Lorsqu’un module a besoin de son propre déploiement, de son propre stockage ou de son propre profil de montée en charge, extrayez-le avec moins de remous.

Rust et Go deviennent alors des outils pour des tâches précises. Utilisez Go là où la vitesse de l’équipe, les services HTTP et la simplicité opérationnelle comptent. Utilisez Rust là où un chemin critique a besoin de sécurité mémoire, d’un contrôle fin des ressources ou de performances prévisibles. N’utilisez les deux que lorsque l’équipe peut soutenir les deux chaînes d’outils, les parcours de recrutement, les systèmes de build et les playbooks d’incident.

Un backend grandit bien lorsque les développeurs le découpent pour des pressions qu’ils peuvent nommer. La frontière de service devrait réduire la coordination, isoler l’échelle ou protéger un invariant critique. Si elle ne fait rien de tout cela, gardez le code ensemble et rendez la frontière du module plus nette.

Commentaires

Chargement des commentaires...