Recherche/Ajouter du code backend et les bons tests à une application multiservices

Ajouter du code backend et les bons tests à une application multiservices

Pour un agent IA qui modifie une application à plusieurs services avec des travailleurs d'arrière-plan. Il vérifie l'environnement et les services, range le code de base de données et de tâches, définit tâches et erreurs selon le projet, choisit un type de test et planifie avant de coder.

Pour utiliser ces règles d'IA
## Préparer l'environnement et vérifier que les services tournent - Les dépendances du backend résident dans un environnement virtuel géré par un fichier de verrouillage, à la racine du dépôt. S'il n'existe pas encore, créez-le à partir du fichier de verrouillage figé et activez-le avant d'exécuter quoi que ce soit. - Les identifiants et les clés nécessaires aux tests résident dans le fichier d'environnement local du dépôt. Lisez-les depuis cet endroit au lieu d'inventer des valeurs ou d'en demander de nouvelles. - Supposez que tous les services applicatifs sont déjà en cours d'exécution, mais vérifiez au lieu de faire confiance : consultez le répertoire des journaux et confirmez que le service concerné émet toujours des entrées. - Atteignez la base de données via le conteneur en cours d'exécution en utilisant l'invocation du client documentée par le projet, et non via une connexion improvisée de votre cru. - Lorsque vous sollicitez l'application web par automatisation du navigateur, connectez-vous avec le compte que crée la configuration de bout en bout. S'il n'existe pas, enregistrez-le via la page d'inscription, puisque le premier compte enregistré devient l'administrateur. ## Garder les requêtes de base de données dans une seule couche et envoyer les appels de test via le frontend - Placez chaque opération de base de données dans les répertoires d'accès aux données prévus à cet effet. N'exécutez pas de requêtes depuis un autre endroit, afin que la connaissance du schéma reste dans une seule couche et puisse être revue en un seul endroit. - Lorsque vous appelez le backend pendant des tests manuels ou scriptés, passez toujours par l'origine du frontend plutôt que directement par le port du backend. Une requête qui contourne le frontend contourne aussi le proxy, la session et le comportement d'authentification que traverse le trafic réel : le résultat n'est donc pas représentatif. ## Placer le nouveau travail dans le pool de travailleurs d'arrière-plan qui lui correspond Le travail asynchrone est réparti entre des pools de travailleurs spécialisés plutôt que dans une file d'attente générale unique. Placez tout nouveau travail dans le pool dont le profil lui correspond. - Un pool principal coordonne les opérations à l'échelle du système : suppression de ressources, synchronisation, élagage et vérifications périodiques. - Les pools de récupération tirent les données depuis des sources externes et engendrent une tâche de traitement par lot, avec une surveillance de type watchdog afin qu'une source bloquée soit détectée. - Les pools de traitement exécutent le pipeline d'ingestion : persister les enregistrements, les découper en fragments, les enrichir, calculer les plongements, écrire dans l'index de recherche et mettre à jour les métadonnées. - Un pool léger prend en charge les opérations courtes à forte concurrence. Un pool lourd prend en charge les opérations gourmandes en ressources à faible concurrence. - Un pool de surveillance, à un seul fil d'exécution parce que la surveillance n'a besoin d'aucun parallélisme, collecte la profondeur des files d'attente, la mémoire et les métriques de santé. - Un pool d'ordonnancement possède les déclencheurs périodiques, des vérifications à intervalle mesurées en secondes jusqu'aux nettoyages horaires, et résout le locataire pour chaque tâche planifiée. Propriétés communes à tous les pools : des pools de threads plutôt que des processus, pour la stabilité, une isolation par locataire, des files d'attente à priorité, des battements de cœur et des contrôles de vivacité, une reprise automatique après échec avec nouvelle tentative, un magasin partagé rapide pour la coordination entre processus, et la base de données relationnelle pour l'état durable des tâches. ## Donner à chaque tâche d'arrière-plan une expiration et son propre délai maximal - Déclarez les tâches avec le décorateur indépendant de l'application fourni par le framework, plutôt que de les lier à une instance concrète de l'application : ainsi, importer une tâche n'entraîne jamais la construction de l'application. - Conservez les modules de tâches dans les répertoires de tâches prévus pour leur édition ou leur niveau. - Ne mettez jamais une tâche en file d'attente sans expiration. Fournissez-en une, que la tâche soit envoyée depuis la planification ou depuis une autre tâche. Soumettre du code qui met en file d'attente sans expiration n'est pas acceptable, car cela laisse la file croître sans limite. - Parce que les tâches s'exécutent dans des pools de threads, les fonctionnalités de limite de temps propres au framework sont silencieusement désactivées. Implémentez la logique de délai d'expiration à l'intérieur de la tâche elle-même. - Les travailleurs ne rechargent pas le code lorsqu'il change. Après avoir modifié du code de travailleur, demandez au mainteneur de redémarrer le travailleur avant de tester, et ne supposez pas que le nouveau code est actif. ## Ajouter des points de terminaison et lever les erreurs avec le type d'erreur du projet - Lorsque vous ajoutez un point de terminaison, typez directement la fonction de gestion au lieu de déclarer un champ de modèle de réponse distinct. - Levez le type d'erreur propre au projet au lieu de l'exception HTTP générique du framework, et ne codez jamais en dur les codes de statut ni n'importez les constantes de statut du framework. - Un gestionnaire global convertit l'erreur du projet en un corps de réponse standard unique portant un code d'erreur et un détail. Cela supprime le code répétitif et maintient une gestion des erreurs cohérente dans tout le backend. - Les codes d'erreur proviennent d'un registre central. Si une nouvelle catégorie est nécessaire, ajoutez-la d'abord au registre ; n'inventez jamais un code au site d'appel. - Lorsque vous relayez une défaillance en amont dont le statut provient de la réponse amont, transmettez ce statut via l'argument de remplacement du statut du type d'erreur, plutôt que d'abandonner ce type d'erreur. ## Garder un typage strict et n'écrire que des commentaires qui restent utiles - Installez et exécutez les hooks de pré-commit du dépôt sur tous les fichiers avant de proposer une modification. - Gardez tout strictement typé, tant côté backend que côté frontend. Les échappatoires non typées ne sont pas un raccourci acceptable. - Gardez les commentaires brefs et limités aux informations qui restent pertinentes sur le long terme. N'écrivez pas de commentaires qui racontent la modification en cours, comme ce qui vient d'être ajouté, supprimé ou remanié ; ils deviennent du bruit dès l'instant où la modification est intégrée. ## Écrire à la main les migrations de base de données pour le schéma par défaut et le schéma privé - Générez le fichier de révision avec l'outil de migration, puis écrivez manuellement le corps de la migration dans le fichier qu'il crée. Ne comptez pas sur l'autogénération pour exprimer l'intention. - Les déploiements multilocataires exigent que la migration soit appliquée à la configuration de schéma privé en plus de la configuration par défaut, et la même distinction s'applique lors de la création de la révision. - Traitez la migration comme faisant partie de la modification soumise à revue, et non comme un ajout après coup appliqué ultérieurement. ## Choisir le seul type de test qui prouve la modification, avec peu de simulation Quatre niveaux existent. Préférez les niveaux à plus haute fidélité à une simulation massive, et choisissez celui qui prouve réellement le comportement. - Les tests unitaires supposent qu'aucun service n'est joignable et simulent chaque interaction avec le monde extérieur. Ne les écrivez que pour des modules complexes et isolés dont la logique mérite d'être fixée pour elle-même. - Les tests unitaires adossés aux dépendances supposent que les dépendances externes fonctionnent — base de données, cache, stockage d'objets, index de recherche, réseau sortant — alors que les conteneurs applicatifs, eux, ne fonctionnent pas. Les fonctions sont appelées directement et certains composants peuvent encore être simulés. Utilisez-les pour minimiser la simulation tout en gardant la possibilité de contrôler un comportement instable ou de vérifier qu'un appel interne a bien eu lieu avec des arguments précis, ce qu'un test d'intégration pur ne peut pas faire. - Les tests d'intégration s'exécutent contre un déploiement réel et ne simulent rien. Préférez ceux-ci, en vous repliant sur les tests unitaires adossés aux dépendances lorsque la simulation ou la vérification interne est réellement nécessaire. - Les tests pilotés par navigateur ajoutent le serveur web par-dessus le déploiement complet. Utilisez-les pour les comportements qui exigent une coordination substantielle entre le frontend et le backend. Ne testez pas à l'excès. Une modification donnée n'a généralement besoin que d'un seul type de test, pas des quatre. ## Réutiliser les fixtures et assistants existants pour écrire des tests d'intégration - Avant d'écrire un test d'intégration, lisez la configuration de test racine pour connaître les fixtures disponibles, ainsi que le répertoire des utilitaires partagés pour connaître les assistants. - Préférez les assistants « manager » de ces utilitaires à l'appel direct des points de terminaison avec une bibliothèque HTTP brute, et préférez les fixtures à l'appel manuel de ces assistants. Créer un administrateur à la main alors qu'une fixture d'administrateur existe duplique la mise en place et s'écarte du reste de la suite. - Les tests sont parallélisés au niveau du répertoire : les fichiers d'un même répertoire ne doivent donc pas dépendre d'un état mutable partagé. - Lorsqu'un test effectue un véritable appel de modèle, utilisez la gamme économique et rapide de chaque fournisseur plutôt qu'un modèle phare. Le coût des tests croît avec la suite, et ce qui est vérifié ne dépend pas de la puissance du modèle. - Copiez la forme d'un test exemplaire existant de la même catégorie plutôt que d'inventer une nouvelle structure. ## Lire les journaux de chaque service avant de deviner la cause d'un échec - Lorsque vous écrivez des tests d'intégration ou que vous pilotez le système en cours d'exécution à la main, lisez les fichiers de journaux de débogage par service dans le répertoire des journaux. Chaque service y déverse sa sortie en continu. - Lisez les journaux avant de formuler une théorie sur une défaillance. Une cause sans rapport, comme un service qui n'est pas en cours d'exécution, ressemble exactement à un vrai défaut tant que le journal n'a pas été lu. ## Garder les clés d'API hors du dépôt et protéger les nouveaux points de terminaison - Ne validez jamais de clés d'API ni de secrets dans le dépôt. - Stockez les identifiants d'intégration via le stockage chiffré des identifiants, jamais dans une configuration en clair. - Suivez les schémas établis de contrôle d'accès basé sur les rôles lorsque vous ajoutez une fonctionnalité. Un nouveau point de terminaison n'hérite d'aucune protection par défaut. - Validez les entrées avec des modèles de schéma à la frontière. - Utilisez des requêtes paramétrées afin que l'injection soit impossible par construction, et n'assemblez jamais une requête par concaténation de chaînes. ## Étiqueter chaque appel à un modèle d'IA avec un nom d'opération enregistré pour le traçage Chaque appel à un modèle de langage, un modèle de plongement, un modèle de réordonnancement, un générateur d'images, un composant vocal ou un classificateur d'intention doit ouvrir un span de génération étiqueté avec une valeur issue du registre central des flux. Utilisez l'enveloppe correspondant au chemin d'appel : l'une pour les appels effectués via l'abstraction de modèle, l'autre pour les appels directs au fournisseur ou en HTTP qui la contournent. - Ajoutez une nouvelle valeur au registre avant d'instrumenter une nouvelle opération. Ne passez jamais une chaîne brute comme étiquette. - Les étiquettes nomment l'opération, comme le réordonnancement ou l'édition d'image, et non le fournisseur. Le fournisseur a sa place dans les métadonnées de configuration du modèle. - Le repli automatique émet des valeurs sentinelles non étiquetées pour les appels qui atteignent l'abstraction sans span explicite. Ces valeurs sentinelles sont visibles dans les tableaux de bord et signifient qu'il manque de l'instrumentation. Corrigez le site d'appel au lieu de vous en remettre au repli. ## Explorer le code, puis rédiger un plan sans calendrier ni code Faites des recherches avant d'écrire. Explorez d'abord les parties pertinentes de la base de code ; un plan écrit à partir d'hypothèses est pire que pas de plan du tout. Un plan comprend au moins les éléments suivants : - Les problèmes à traiter : ce que la modification est censée faire. - Les remarques importantes : les éléments découverts pendant les recherches qui comptent pour la mise en œuvre. - La stratégie de mise en œuvre : l'approche de haut niveau pour concrétiser la modification. - Les tests : quels tests vérifieront le comportement correct, choisis délibérément et réduits au minimum. N'incluez pas de section calendrier, et n'incluez pas de traitement des étapes de retour en arrière. N'écrivez pas de code dans le cadre du plan ; restez à un niveau élevé, même s'il est utile de faire référence à des fichiers ou à des fonctions précis. Cette liste est un minimum : ajoutez-en davantage lorsque cela aide. ## Lire les règles propres à un sous-projet avant d'y travailler - Les consignes à la racine couvrent les préoccupations partagées. Chaque sous-projet conserve son propre document de normes à côté de son code, et ce document régit sa propre arborescence. - Lisez le document délimité avant de travailler dans ce domaine. Ne supposez pas que les règles d'un client s'appliquent à un autre : un client mobile sans modèle objet de document, dont les primitives de style ont des valeurs d'espacement absolues plutôt qu'une échelle relative par paliers, et qui possède ses propres primitives de routage et de vue, suit des règles différentes de celles d'un client web. - Les pratiques d'ingénierie plus larges résident dans le guide de contribution du dépôt. Lisez-le et suivez-le en complément du présent document.