mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-27 14:58:16 +02:00
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>
441 lines
16 KiB
Markdown
441 lines
16 KiB
Markdown
# Backend — Risques & vigilance : Général
|
|
|
|
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet.
|
|
|
|
---
|
|
|
|
<a id="risque-observabilite-insuffisante"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-migrations-risquees"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-upsert-n-plus-un-provider"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-authorize-after-fetch"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-valeur-sentinelle-dto-date"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-endpoint-idempotent-sans-doublon"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-fichier-orphelin-echec-persistance"></a>
|
|
## 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
|
|
|
|
```ts
|
|
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
|
|
|
|
---
|
|
|
|
<a id="risque-melange-date-locale-utc-seed"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-champ-fantome-zod-non-persiste"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-catch-vide-sans-logging"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-params-route-non-valides"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-cast-ts-brut-valeurs-db"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-creation-ressource-temporelle-sans-chevauchement"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-toctou-operations-conditionnelles"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-biais-agregation-moyenne-ratio"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-couplage-semantique-types-erreur"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-service-http-aware"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-count-sans-filtre-user"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-env-top-level-module"></a>
|
|
## 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
|