Files
_Assistant_Lead_Tech/knowledge/backend/risques/general.md
MaksTinyWorkshop 72758c1adc capitalisation: intégration 33 propositions RL799_V2 (triage 2026-04-07)
Backend: 21 entrées (general, prisma, contracts, auth, patterns)
Frontend: 9 entrées (navigation, tests, general, performance, patterns)
Workflow: 5 entrées (story-tracking)
Nouveau fichier: backend/patterns/general.md
95_a_capitaliser.md purgé.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:44:36 +02:00

16 KiB

Backend — Risques & vigilance : Général

Extrait de la base de connaissance Lead_tech. Voir knowledge/backend/risques/README.md pour l'index complet.


Observabilité insuffisante (logs non structurés, pas de corrélation)

Risques

  • MTTR très élevé : on devine
  • Incapacité à mesurer l'impact utilisateur

Symptômes

  • Logs "ça a crash" sans contexte
  • Impossible de relier une requête à une erreur
  • Latence qui dérive sans alerte

Bonnes pratiques / mitigations

  • Logs structurés + requestId/traceId
  • Métriques de base (latence, erreurs, throughput)
  • Alertes simples sur 5xx/latence

Migrations risquées / non reproductibles

Risques

  • Downtime
  • Perte de données
  • Incohérence entre environnements

Symptômes

  • "Ça marche en local" mais pas en prod
  • Migration qui échoue à mi-chemin
  • Rollback impossible

Bonnes pratiques / mitigations

  • Migrations versionnées + tests staging
  • Stratégie expand/contract si besoin
  • Plan de rollback/mitigation

Boucle upsert N+1 sur synchronisation provider

Risques

  • Latence multipliée par le nombre d'items
  • Charge DB inutile
  • Timeouts ou contention sur gros volumes

Symptômes

  • Une boucle applicative exécute un upsert par item
  • Temps de traitement qui explose avec le volume
  • Logs SQL répétitifs et séquentiels

Bonnes pratiques / mitigations

  • Batcher quand c'est possible
  • Précharger les données nécessaires avant boucle
  • Mesurer explicitement le coût d'un upsert unitaire dans les flux de sync
  • Contexte technique : Prisma / synchronisation provider — 10-03-2026

Anti-pattern : Authorize-after-fetch (contrôle d'accès après chargement)

Risques

  • Le contrôle d'accès effectué après chargement des relations métier volumineuses augmente le coût DB et la surface de fuite temporelle pour des requêtes finalement refusées

Symptômes

  • Handler qui charge un agrégat complet (avec includes/relations) puis vérifie l'accès
  • Requêtes refusées après un temps de réponse anormalement long

Bonnes pratiques / mitigations

  • Pour les endpoints détail sensibles, filtrer l'accès dans la requête DB (where + scope grade/tenant) ou faire un pré-check minimal avant de charger les relations

  • Les accès non autorisés ne doivent pas déclencher un fetch complet des données métier

  • Contexte technique : backend général — RL799_V2 02-04-2026


Valeur sentinelle "" au lieu de null dans un DTO de date nullable

Risques

  • Un champ de date optionnel dans le modèle Prisma (Date | null) sérialisé en "" dans le DTO au lieu de null
  • Le type DTO ment (string au lieu de string | null), et tout consommateur testant if (profile.field) a un comportement incorrect sur ""

Symptômes

  • ?? '' sur une date issue de ?.toISOString() dans un repository Prisma
  • Type DTO avec someDate: string non optionnel pour un modèle qui autorise null en DB

Bonnes pratiques / mitigations

  • Si un champ de date peut être null en DB, le type DTO doit être string | null

  • Le repository utilise ?? null (pas ?? '') pour les dates nulles

  • Le frontend utilise ?? (nullish coalescing) pas || pour les fallbacks d'affichage sur ces champs

  • Contexte technique : Prisma / DTO — RL799_V2 02-04-2026


Endpoint idempotent sans contrôle de doublon

Risques

  • Un endpoint crée un event/enregistrement censé être unique par contexte (ex: un vote par tenue, un PDF par planche) sans vérifier l'existence préalable
  • Un double-clic ou retry réseau crée des doublons silencieux, ou des fichiers orphelins en cas d'artefact physique

Symptômes

  • writeFile + prisma.create sans findFirst préalable dans le même handler
  • Plusieurs enregistrements identiques avec le même contexte en base
  • Fichiers physiques (PDF, export) dupliqués sur disque

Bonnes pratiques / mitigations

  • Avant tout create d'un event contextuel, lire les events existants avec le même filtre de contexte

  • Retourner 409 si l'event existe déjà, ou mettre à jour l'existant selon la sémantique métier

  • Pour les artefacts physiques : retourner l'existant (200) au lieu de recréer (201), ou supprimer l'ancien avant d'en créer un nouveau

  • Contexte technique : backend général — RL799_V2 03-04-2026


Fichier orphelin sur échec de persistance en base

Risques

  • Un service écrit un fichier sur disque puis crée l'entrée correspondante en base ; si la création en base échoue, le fichier reste orphelin
  • Un catch {} vide rend le debug impossible

Symptômes

  • Fichiers physiques présents sur disque sans enregistrement correspondant en base
  • Erreurs de persistance invisibles dans les logs

Bonnes pratiques / mitigations

const file = await writeFileToDisk(...);
try {
  await createDbRecord(...);
} catch (err) {
  await cleanupFile(file.path);
  console.error('Erreur persistance:', err);
  throw err;
}
  • Si la création en base échoue, supprimer le fichier physique (cleanup)

  • Toujours logger l'erreur dans le catch, même si on retourne une réponse d'erreur propre au client

  • Contexte technique : backend / fichiers — RL799_V2 03-04-2026


Mélange Date locale / Date UTC dans les données de seed

Risques

  • Des dates dynamiques construites via new Date(year, month, day, hour) (heure locale) cohabitent avec des dates fixes en UTC (new Date('...T19:00:00Z')) dans le même fichier
  • L'incohérence est silencieuse et produit des décalages horaires non reproductibles entre environnements

Symptômes

  • new Date(year, month, day, hour) (local) dans un fichier qui utilise aussi new Date('...Z') (UTC)
  • Données de seed dont les heures varient selon la timezone de l'environnement dev

Bonnes pratiques / mitigations

  • Les dates dynamiques dans un seed doivent être construites en UTC (Date.UTC(...)) si les dates fixes sont en UTC

  • Signal review : new Date(year, month, day, hour) dans un fichier qui utilise aussi new Date('...Z')

  • Contexte technique : Node.js / seed — RL799_V2 03-04-2026


Champ fantôme accepté par validation Zod mais non persisté

Risques

  • Un champ optionnel ajouté dans un schéma Zod est accepté par la validation mais jamais exploité par la chaîne handler → repository → modèle Prisma
  • L'utilisateur envoie une donnée sans erreur, mais elle est silencieusement perdue

Symptômes

  • Champ optionnel dans le schéma Zod absent du modèle Prisma
  • Champ passé au service mais non inclus dans l'appel create/update du repository

Bonnes pratiques / mitigations

  • Avant d'ajouter un champ au schéma de validation, s'assurer que le modèle DB le supporte

  • Si le modèle ne le supporte pas, ne pas accepter le champ du tout

  • Signal review : champ dans le schéma Zod qui n'apparaît pas dans le data de l'appel Prisma

  • Contexte technique : Zod / Prisma — RL799_V2 06-04-2026


Catch vide avalant les exceptions sans logging

Risques

  • Les catch {} ou catch { // ignore } dans les services métier masquent les erreurs réelles
  • En production, un disque plein ou un bug PDFKit retourne un message générique sans trace serveur

Symptômes

  • catch { sans console.error ni logger dans un service métier
  • Bloc catch qui retourne un message d'erreur générique sans capturer err

Bonnes pratiques / mitigations

  • Toujours capturer et logger l'exception dans les catch des services métier

  • Les catch de rollback secondaire (ex: unlink d'un fichier) peuvent être silencieux mais devraient au minimum émettre un warn

  • Signal review : catch { sans variable dans un service métier (hors rollback secondaire)

  • Contexte technique : backend général — RL799_V2 06-04-2026


Paramètres de route non validés avant passage à Prisma

Risques

  • Un paramètre de route (id, memberId) passé directement à prisma.findUnique({ where: { id } }) sans validation de format (UUID, slug)
  • Des chaînes arbitraires atteignent la couche DB, polluant les logs et violant le principe de validation aux frontières

Symptômes

  • const { id } = params; prisma.model.findUnique({ where: { id } }) sans isValidUuid(id)
  • Erreurs Prisma non significatives en production sur des identifiants malformés

Bonnes pratiques / mitigations

  • Toujours valider les identifiants (UUID, slug, etc.) dès l'entrée du handler ou du service, avant tout appel DB

  • Retourner 400 immédiatement si invalide

  • Contexte technique : backend / validation — RL799_V2 07-04-2026


Cast TypeScript brut sur valeurs DB non contraintes

Risques

  • Un cast value as 'expected_a' | 'expected_b' sur un champ Prisma de type String sans enum DB masque silencieusement toute valeur inattendue
  • Le type-checking passe, mais une valeur inattendue en DB provoque des comportements incohérents en aval

Symptômes

  • status as 'pending' | 'late' sur un champ Prisma String sans enum DB
  • Pas de fallback explicite pour les valeurs non attendues

Bonnes pratiques / mitigations

  • Utiliser des conditions explicites (if (s === 'a') ... else if (s === 'b') ... else fallback) ou un mapping validé

  • Ne jamais faire confiance au type runtime via un simple cast TS

  • Signal review : as 'literal_a' | 'literal_b' sur un champ Prisma de type String

  • Contexte technique : TypeScript / Prisma — RL799_V2 07-04-2026


Création de ressource temporelle sans détection de chevauchement

Risques

  • Un endpoint crée une ressource avec startDate/endDate sans vérifier les collisions avec les ressources existantes
  • Un double-clic ou une erreur humaine génère des doublons avec les mêmes dates

Symptômes

  • Deux ressources avec des plages temporelles qui se chevauchent pour le même contexte (cotisations, abonnements, sessions)

Bonnes pratiques / mitigations

  • Avant insertion, query les ressources existantes dont les plages se chevauchent : WHERE start < newEnd AND end > newStart

  • Retourner 409 Conflict si collision

  • S'applique aux cotisations, abonnements, périodes de garde, sessions — tout ce qui a des bornes temporelles

  • Contexte technique : backend général — RL799_V2 07-04-2026


TOCTOU sur opérations conditionnelles sans transaction

Risques

  • findUnique → check status → update en séquence sans transaction crée une fenêtre TOCTOU (Time-of-Check Time-of-Use)
  • Un traitement concurrent peut modifier l'état entre la vérification et la mise à jour

Symptômes

  • Données incohérentes sous charge concurrente (rare en MVP mais critique en prod)
  • Double relance de cotisation, double changement de statut

Bonnes pratiques / mitigations

  • Wrapper dans prisma.$transaction() ou utiliser un UPDATE ... WHERE status != 'paid' atomique

  • Règle : toute séquence read → validate → write sur la même entité doit être atomique

  • Contexte technique : Prisma / backend général — RL799_V2 07-04-2026


Calcul de moyenne vs ratio global — biais d'agrégation

Risques

  • Le ratio global (somme numérateurs / somme dénominateurs) donne un résultat biaisé vers les entités avec les dénominateurs les plus grands quand on veut calculer une "moyenne de taux" sur N entités

Symptômes

  • "% de présence moyenne par degré" calculé comme total_présents / total_inscrits au lieu d'une moyenne par tenue
  • Tests verts car les données de test symétriques donnent le même résultat avec les deux méthodes

Bonnes pratiques / mitigations

  • Moyenne de présence par tenue → calculer le taux par tenue, puis la moyenne des taux — pas le ratio global

  • Rédiger un test qui construit des données asymétriques pour détecter la différence (ex: une tenue avec 10 inscrits/8 présents, une avec 2 inscrits/2 présents → 83% global vs 90% moyenne correcte)

  • Contexte technique : backend / statistiques — RL799_V2 07-04-2026


Couplage sémantique sur les types d'erreur partagés

Risques

  • Un helper générique (ex: authHelpers) importe un type nommé d'après un domaine métier spécifique (ex: ConvocationErrorCode)
  • Chaque nouveau domaine qui utilise l'auth helper "dépend" conceptuellement du module d'origine

Symptômes

  • Un helper générique utilisé par 12+ fichiers importe un type nommé d'après un seul domaine métier
  • Couplage sémantique incohérent hérité de la première implémentation

Bonnes pratiques / mitigations

  • À la prochaine refacto touchant le helper, extraire un type ApiErrorCode générique dans le package partagé et faire pointer les types domaine vers lui

  • Contexte technique : backend / architecture — RL799_V2 07-04-2026


Service HTTP-aware — violation de la séparation des couches

Risques

  • Un fichier service.ts importe Response, construit les headers et status HTTP, et retourne un objet Response
  • Le service devient intestable sans mock HTTP, la logique métier est couplée au transport, et la réutilisation (CLI, cron, WebSocket) est impossible

Symptômes

  • Le fichier route.ts ne fait que (req) => handleX(req) — pass-through d'une ligne
  • Import de Response dans un fichier service

Bonnes pratiques / mitigations

  • Le service doit retourner { data: T } | { error: { code, message } } et la route construit la Response

  • Refactoriser quand un service est touché

  • Contexte technique : architecture en couches — RL799_V2 07-04-2026


Count sans filtre sur User — confusion membres / utilisateurs

Risques

  • prisma.user.count() sans where inclut les comptes techniques (admin, secretaire) dans les comptages métier "nombre de membres inscrits"
  • Les stats métier sont faussées silencieusement

Symptômes

  • prisma.user.count() sans where pour un affichage de type "effectifs inscrits"
  • Écart entre le comptage affiché et la réalité métier

Bonnes pratiques / mitigations

  • Toujours filtrer { where: { role: { not: 'admin' } } } (ou équivalent) pour les comptages métier

  • À terme, ajouter un champ status (active/inactive/suspended) pour distinguer les membres actifs

  • Signal review : prisma.user.count() sans where dans un contexte de statistiques

  • Contexte technique : Prisma / stats — RL799_V2 07-04-2026


Variables d'environnement lues au top-level d'un module

Risques

  • const FOO = process.env.FOO || 'default' en dehors d'une fonction fige la valeur au premier import
  • Les tests qui modifient process.env après l'import ne voient pas l'effet
  • Config non-reloadable sans redémarrage

Symptômes

  • process.env.X assigné à une constante au top-level d'un module Node
  • Tests qui doivent manipuler l'ordre d'import pour surcharger une variable d'environnement

Bonnes pratiques / mitigations

  • Encapsuler dans un getter : const getFoo = () => process.env.FOO || 'default'

  • Permet la surcharge en test et le rechargement dynamique

  • Contexte technique : Node.js / tests — RL799_V2 07-04-2026