Files
_Assistant_Lead_Tech/10_backend_patterns_valides.md
MaksTinyWorkshop 5650f26b08 feat: capitalise Epic 2 app-alexandrie + enrichit post-bmad-install
- Intègre 9 propositions de 95_a_capitaliser.md (Stripe, webhooks, Redis,
  entitlements, guards, catch silencieux, conventions File List)
- Ajoute core-bmad-master dans les agents patchés (orchestrateur)
- Différencie les fichiers cibles par rôle d'agent (dev/architect/qa…)
- Patch dev-story et code-review XML pour déclencher la capitalisation
  à chaque fin de story et après chaque code review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 14:13:34 +01:00

20 KiB
Raw Blame History

Patterns back-end validés

Ce fichier contient uniquement des patterns back-end :

  • testés,
  • validés,
  • utilisés en conditions réelles.

Objectif : éviter de réinventer la roue et réduire le temps de debug.

Dernière mise à jour : 09-03-2026


Index


Règle dor

Si ce nest pas confirmé comme fonctionnel et utile, ça na rien à faire ici.

  • Pas de “bonnes pratiques” vagues
  • Pas de dépendances implicites à une stack
  • Si cest spécifique à un framework / runtime / DB : on le note

Périmètre couvert

  • API (REST/GraphQL), services applicatifs
  • authn/authz
  • contrats (validation / schémas)
  • gestion derreurs
  • DB & migrations
  • observabilité
  • opérations sensibles (idempotence, retries)
  • intégrations (webhooks, jobs async)

Format standard dun pattern

Pattern :

  • Objectif : …
  • Contexte : …
  • Quand lutiliser : …
  • Quand léviter : …
  • Avantage : …
  • Limites / vigilance : …
  • Validé le : DD-MM-YYYY
  • Contexte technique : (obligatoire) ex. Node 20 / Postgres 16 ou Python 3.12 / FastAPI / Redis

Implémentation (exemple minimal)

(contenu)

Checklist (si pertinente)

  • Erreurs standardisées
  • Validation dentrée (schéma)
  • Observabilité minimale (requestId/traceId + logs)
  • Sécurité (authn/authz + secrets)
  • Tests au bon niveau
  • Idempotence si opération sensible

Pattern : Format derreur API standardisé

  • Objectif : fournir des erreurs prévisibles, exploitables et cohérentes pour tous les clients.
  • Contexte : API consommée par front-end, automatisations ou intégrations externes.
  • Quand lutiliser : dès quune API est exposée à autre chose quun usage interne trivial.
  • Quand léviter : jamais.
  • Avantage :
    • Debug plus rapide
    • UX maîtrisée côté client
    • Observabilité améliorée
  • Limites / vigilance :
    • Discipline requise pour éviter les formats ad hoc
  • Validé le : 25-01-2026
  • Contexte technique : API HTTP agnostique

Implémentation (exemple minimal)

{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "Utilisateur introuvable",
    "requestId": "abc-123"
  }
}

Checklist

  • Codes HTTP cohérents (4xx / 5xx)
  • Codes derreur applicatifs stables
  • Message utilisateur non technique
  • requestId présent

Pattern : Middleware de corrélation (requestId / traceId)

  • Objectif : relier chaque requête aux logs et erreurs associées.
  • Contexte : toute API ou service exposé.
  • Quand lutiliser : systématiquement en production.
  • Quand léviter : jamais.
  • Avantage :
    • MTTR réduit drastiquement
    • Debug cross-services possible
  • Limites / vigilance :
    • Doit être propagé partout (logs, erreurs, appels sortants)
  • Validé le : 25-01-2026
  • Contexte technique : Backend agnostique (HTTP)

Implémentation (exemple minimal)

- Générer un requestId à lentrée si absent
- Le propager dans le contexte de requête
- Linclure dans chaque log et réponse derreur

Checklist

  • requestId généré ou repris dun header existant
  • Présent dans tous les logs
  • Présent dans les erreurs retournées

Pattern : Idempotency key pour opérations sensibles

  • Objectif : empêcher les doublons lors de retries ou timeouts.
  • Contexte : création de ressources, paiements, webhooks.
  • Quand lutiliser : toute opération non strictement en lecture.
  • Quand léviter : endpoints purement GET.
  • Avantage :
    • Protection contre doublons
    • Robustesse face aux retries
  • Limites / vigilance :
    • Stockage et expiration des clés à gérer
  • Validé le : 25-01-2026
  • Contexte technique : API HTTP + DB transactionnelle

Implémentation (exemple minimal)

- Client fournit Idempotency-Key
- Backend stocke la clé + résultat
- Retry retourne le résultat initial

Checklist

  • Clé obligatoire sur endpoints sensibles
  • Contrainte dunicité côté DB
  • Comportement documenté

Pattern : Pagination robuste (cursor-based) pour les listings

  • Objectif : fournir des listings stables et performants sans incohérences entre pages.
  • Contexte : endpoints de liste (ex. /users, /orders) avec volume potentiellement important.
  • Quand lutiliser : dès quun listing peut dépasser quelques dizaines/centaines ditems ou subir des écritures concurrentes.
  • Quand léviter : listes strictement petites et statiques.
  • Avantage :
    • Résultats stables malgré insertions/suppressions
    • Meilleure performance que loffset sur gros volumes
    • Expérience client plus fiable
  • Limites / vigilance :
    • Nécessite un tri déterministe (champ + tie-breaker)
    • Complexité légèrement supérieure à offset/limit
  • Validé le : 25-01-2026
  • Contexte technique : API HTTP + DB (Postgres/MySQL), agnostique framework

Implémentation (exemple minimal)

- Trier par (createdAt DESC, id DESC) (exemple)
- Le client envoie cursor = dernier (createdAt,id) reçu
- Le backend renvoie nextCursor si plus de résultats
- Ne jamais exposer de cursor implicite ou non documenté

Checklist

  • Tri déterministe (avec tie-breaker)
  • nextCursor renvoyé et documenté
  • Limite max de page (protection)
  • Index DB aligné avec le tri

Pattern : Exécution asynchrone des tâches longues (queue + outbox light)

  • Objectif : sortir les opérations longues ou fragiles du chemin request/response.
  • Contexte : envoi demails, appels SaaS, génération de PDF, traitements batch, webhooks sortants.
  • Quand lutiliser : dès quune opération peut dépasser la latence acceptable ou dépendre dun service externe.
  • Quand léviter : opérations réellement instantanées et sans dépendances externes.
  • Avantage :
    • API plus rapide et plus fiable
    • Retries maîtrisés
    • Meilleure résilience aux pannes externes
  • Limites / vigilance :
    • Demande une discipline stricte sur lidempotence
    • Nécessite une stratégie minimale de dead-letter ou dalerting
  • Validé le : 25-01-2026
  • Contexte technique : Backend agnostique + DB transactionnelle + worker

Implémentation (exemple minimal)

- API écrit un job ou event en DB dans la transaction métier
- Worker lit les jobs en attente et exécute
- Retries avec backoff + compteur
- Statut FAILED ou dead-letter + alerte
- Idempotence par clé métier ou idempotency key

Checklist

  • Job créé dans une transaction (évite les pertes)
  • Retries et backoff définis
  • Dead-letter ou statut FAILED visible
  • Idempotence garantie
  • Logs corrélés (requestId/traceId)

Pattern : Soft delete et archivage explicite

  • Objectif : permettre la suppression logique sans perte immédiate de données.
  • Contexte : données métier critiques, besoins daudit, restauration ou conformité.
  • Quand lutiliser : dès quune suppression peut avoir des impacts métier ou légaux.
  • Quand léviter : données purement techniques ou réellement éphémères.
  • Avantage :
    • Restauration possible
    • Audit et traçabilité
    • Réduction des suppressions irréversibles
  • Limites / vigilance :
    • Complexité accrue sur les requêtes
    • Nécessite une discipline stricte (filtres par défaut)
  • Validé le : 25-01-2026
  • Contexte technique : API + DB relationnelle

Implémentation (exemple minimal)

- Champ deletedAt (nullable) ou status
- Les requêtes standards filtrent deletedAt IS NULL
- Endpoints dédiés pour restauration / purge
- Index DB tenant compte du soft delete

Checklist

  • Filtrage soft delete par défaut
  • Restauration explicite possible
  • Purge maîtrisée (cron / job)
  • Index DB adaptés
  • Tests sur cas supprimé / restauré

Pattern : Webhooks sortants robustes et idempotents

  • Objectif : garantir des intégrations fiables avec des systèmes externes.
  • Contexte : notifications, synchronisations, événements métier sortants.
  • Quand lutiliser : dès quun événement doit être transmis à un tiers.
  • Quand léviter : intégrations strictement synchrones et internes.
  • Avantage :
    • Tolérance aux pannes réseau
    • Retries maîtrisés
    • Observabilité des échecs
  • Limites / vigilance :
    • Gestion des retries et du volume
    • Nécessite une idempotence côté consommateur
  • Validé le : 25-01-2026
  • Contexte technique : Backend + HTTP + worker/queue

Implémentation (exemple minimal)

- Événement persisté (outbox) en DB
- Envoi asynchrone via worker
- Retries avec backoff
- Signature du payload (HMAC)
- Idempotency key dans le header

Checklist

  • Payload signé et vérifiable
  • Retries + backoff définis
  • Dead-letter ou statut FAILED visible
  • Idempotence documentée
  • Logs corrélés (requestId/traceId)

Pattern : Contracts-First / Zod-Infer / No-DTO (monorepo TypeScript fullstack)

  • Objectif : avoir une seule source de vérité pour les contrats dinterface entre API et client, sans redéfinition manuelle de types.
  • Contexte : monorepo TypeScript avec un package partagé (packages/contracts ou équivalent), consommé par le backend et le front/mobile.
  • Quand lutiliser : dès quune API est consommée par un client TypeScript dans le même repo.
  • Quand léviter : si le client est externe (autre organisation, autre langage) — dans ce cas, OpenAPI reste la référence.
  • Avantage :
    • Zéro drift entre contrat et implémentation
    • Types TypeScript gratuits via z.infer<> — aucune réécriture
    • Changement de contrat = erreur de compilation immédiate côté client
    • Mocks de tests alignés automatiquement
  • Limites / vigilance :
    • Ne pas mettre de logique métier dans packages/contracts (IO only)
    • Attention aux dépendances circulaires si le package grossit
  • Validé le : 07-03-2026
  • Contexte technique : TypeScript / Zod / NestJS + Expo (React Native) — pattern agnostique framework

Implémentation (exemple minimal)

// packages/contracts/src/auth/auth.schemas.ts
export const RegisterRequestSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});
export type RegisterRequest = z.infer<typeof RegisterRequestSchema>; // type GRATUIT

// packages/contracts/src/index.ts
export * from ./auth/auth.schemas;
export * from ./errors/error-code;

// apps/api/src/modules/auth/auth.controller.ts
import type { RegisterRequest } from @monrepo/contracts;
// + ZodValidationPipe → validation automatique, zéro DTO manuel

// apps/mobile/src/domains/auth/auth.store.ts
import type { RegisterRequest } from @monrepo/contracts;
// même type, même schéma, zéro duplication

Structure cible du package contracts

packages/contracts/src/
  auth/auth.schemas.ts       ← request/response auth
  users/users.schemas.ts     ← request/response users
  billing/billing.schemas.ts ← request/response billing (Epic suivant)
  errors/error-code.ts       ← enum codes derreur stables
  http/envelopes.ts          ← { data, meta } / { error, meta }
  index.ts                   ← re-export tout

Ce qui appartient à contracts

  • Schémas Zod request/response
  • Types inférés (z.infer<>)
  • Codes derreur applicatifs stables
  • Enums et constantes partagées (ex : liste officielle de sujets/topics)

Ce qui nappartient PAS à contracts

  • Logique métier
  • Modules/services/guards framework (NestJS, etc.)
  • State management client (Zustand, Redux, etc.)

Checklist

  • Zéro DTO manuel dans lAPI — uniquement z.infer<typeof Schema>
  • ZodValidationPipe global ou par endpoint pour la validation dentrée
  • Constantes partagées (enums, listes) dans contracts, jamais dupliquées
  • Mocks de tests importent les types depuis contracts

Pattern : Guard global NestJS — ordre denregistrement et décorateurs de bypass

  • Objectif : protéger tous les endpoints par défaut, avec un mécanisme explicite pour les exceptions.
  • Contexte : API NestJS avec plusieurs guards globaux (authn, authz, feature flags...).
  • Quand lutiliser : dès quon a 2+ guards globaux dont lun dépend du résultat de lautre.
  • Quand léviter : si un seul guard suffit.
  • Avantage :
    • Sécurité par défaut (opt-out, pas opt-in)
    • Ordre dexécution garanti et explicite
    • Bypass documenté et traçable via décorateurs
  • Limites / vigilance :
    • Lordre des APP_GUARD dans providers[] est lordre dexécution — ne pas inverser
    • Exporter le service depuis son module si injecté dans un guard global dun autre module
  • Validé le : 07-03-2026
  • Contexte technique : NestJS v10+

Implémentation (exemple minimal)

// app.module.ts
providers: [
  { provide: APP_GUARD, useClass: AuthGuard },          // 1er : peuple request.user
  { provide: APP_GUARD, useClass: EmailVerifiedGuard }, // 2ème : lit request.user
  { provide: APP_GUARD, useClass: EntitlementsGuard },  // 3ème : lit request.user + entitlements
]

// skip-auth.decorator.ts
export const SKIP_AUTH = skipAuth;
export const SkipAuth = () => SetMetadata(SKIP_AUTH, true);

// auth.guard.ts
const skip = this.reflector.getAllAndOverride<boolean>(SKIP_AUTH, [
  context.getHandler(),
  context.getClass(), // permet @SkipAuth() au niveau classe
]);
if (skip) return true;

Checklist

  • AuthGuard enregistré en premier dans providers[]
  • AuthModule exporte AuthService si AuthGuard est dans AppModule
  • Décorateur @SkipAuth() sur tous les endpoints publics (auth, health, docs)
  • Tests unitaires sur le guard avec reflector mocké

Pattern : Provider-Strategy pour intégrations tierces — périmètre complet

  • Objectif : isoler intégralement la logique propre à un prestataire (Stripe, Brevo, Firebase…) derrière une interface stable, pour éviter la contamination du domaine par le SDK tiers.
  • Contexte : backend NestJS/TypeScript avec 1+ prestataires externes (paiement, email, storage…).
  • Quand lutiliser : dès quun service applicatif dépend dun SDK tiers (et plus encore sil y a des webhooks).
  • Quand léviter : intégration ponctuelle non critique sans effet de bord (rare) — sinon on perd vite le contrôle.
  • Avantage :
    • Testabilité : mock du provider, pas du SDK
    • Remplacement du prestataire sans refactor “en cascade”
    • Responsabilités claires : provider = “parle Stripe”, service = “parle domaine”
  • Limites / vigilance :
    • Linterface doit exposer des types normalisés (pas de types Stripe)
    • Le provider gère aussi les webhooks : validation signature, parsing event, mapping
  • Validé le : 09-03-2026
  • Contexte technique : NestJS v10+ / intégration Stripe (webhooks) — pattern généralisable

Implémentation (exemple minimal)

// billing-provider.interface.ts (pas d'import Stripe)
export type BillingPlan = 'MONTHLY' | 'ANNUAL';

export type BillingWebhookResult = {
  userId: string;
  externalId: string;
  plan: BillingPlan;
  status: 'ACTIVE' | 'INACTIVE' | 'CANCELLED';
  currentPeriodEnd: Date | null;
};

export interface BillingProvider {
  createCheckoutSession(userId: string, plan: BillingPlan): Promise<{ checkoutUrl: string }>;
  cancelSubscription(externalId: string): Promise<void>;
  handleWebhook(rawBody: Buffer, signature: string): Promise<BillingWebhookResult | null>;
}

// billing.service.ts (domaine uniquement)
async handleWebhook(rawBody: Buffer, signature: string): Promise<void> {
  const result = await this.billingProvider.handleWebhook(rawBody, signature);
  if (!result) return;
  await this.prisma.subscription.upsert({ /* données normalisées */ });
}

Pattern : Stripe — metadata sur subscription_data, pas sur la Session

  • Objectif : garantir que userId (ou tout identifiant métier) soit accessible dans les events customer.subscription.*, pas seulement dans checkout.session.completed.
  • Contexte : intégration Stripe Checkout avec webhooks abonnement.
  • Quand lutiliser : systématiquement dès quon crée une Checkout Session liée à une Subscription.
  • Risque si ignoré : metadata.userId absent des events customer.subscription.updated/deleted → silent failure en prod.
  • Validé le : 09-03-2026
  • Contexte technique : Stripe API v17+ / NestJS

Implémentation

stripe.checkout.sessions.create({
  metadata: { userId },                           // pour checkout.session.completed
  subscription_data: { metadata: { userId } },    // pour customer.subscription.*
});

Pattern : Webhooks entrants — parsing unique (single constructWebhookEvent)

  • Objectif : appeler constructWebhookEvent une seule fois par requête, puis router vers des extracteurs purs.
  • Contexte : endpoint webhook recevant des events de plusieurs types (subscription, pack, facture…).
  • Quand lutiliser : dès quon a 2+ handlers webhook sur le même endpoint.
  • Risque si ignoré : double vérification de signature + états partiels possibles (sub OK / pack KO).
  • Validé le : 09-03-2026
  • Contexte technique : Stripe / NestJS

Implémentation

// 1. Parser unique — 1 seul constructWebhookEvent(rawBody, sig) → event opaque
// 2. Extracteurs purs, sans effet de bord :
handleSubscriptionWebhookEvent(event): WebhookResult | null
handlePackWebhookEvent(event): PackWebhookResult | null
// 3. Orchestrateur unique appelle les extracteurs, persiste les résultats

Pattern : Contracts-First — error codes comme contrat obligatoire

  • Objectif : maintenir les codes derreur API dans packages/contracts pour éviter les clients stringly-typed.
  • Contexte : monorepo TypeScript avec packages/contracts/src/errors/error-code.ts.
  • Règle : toute nouvelle erreur API ⇒ ajout obligatoire dans error-code.ts avant merge, pas après.
  • Risque si ignoré : clients qui testent des strings hardcodées au lieu dimporter lenum → drift silencieux.
  • Validé le : 09-03-2026
  • Contexte technique : TypeScript / NestJS + Expo (React Native)

Checklist

  • Nouvel error.code → ajout dans packages/contracts/src/errors/error-code.ts en même commit
  • Clients importent lenum, pas une string littérale
  • PR review : vérifier error-code.ts à chaque ajout dendpoint derreur

Notes importantes

  • On préfère 5 patterns solides à 50 “bons conseils”.
  • Un pattern = une idée actionnable + son cadre dutilisation.