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>
16 KiB
Backend — Risques & vigilance : Général
Extrait de la base de connaissance Lead_tech. Voir
knowledge/backend/risques/README.mdpour 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
upsertpar 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
upsertunitaire 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 denull - Le type DTO ment (
stringau lieu destring | null), et tout consommateur testantif (profile.field)a un comportement incorrect sur""
Symptômes
?? ''sur une date issue de?.toISOString()dans un repository Prisma- Type DTO avec
someDate: stringnon optionnel pour un modèle qui autorisenullen DB
Bonnes pratiques / mitigations
-
Si un champ de date peut être
nullen DB, le type DTO doit êtrestring | 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.createsansfindFirstpré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
created'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 aussinew 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 aussinew 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/updatedu 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
datade l'appel Prisma -
Contexte technique : Zod / Prisma — RL799_V2 06-04-2026
Catch vide avalant les exceptions sans logging
Risques
- Les
catch {}oucatch { // 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 {sansconsole.errorni 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 } })sansisValidUuid(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 typeStringsans 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 PrismaStringsans 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 typeString -
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/endDatesans 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 →updateen 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 unUPDATE ... 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
ApiErrorCodegé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.tsimporteResponse, construit les headers et status HTTP, et retourne un objetResponse - 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
Responsedans 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()sanswhereinclut 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()sanswherepour 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()sanswheredans 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 premierimport- Les tests qui modifient
process.envaprès l'import ne voient pas l'effet - Config non-reloadable sans redémarrage
Symptômes
process.env.Xassigné à 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