mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-06 21:41:42 +02:00
248 lines
8.8 KiB
Markdown
248 lines
8.8 KiB
Markdown
# Backend — Patterns : Prisma
|
|
|
|
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet.
|
|
|
|
---
|
|
|
|
<a id="pattern-soft-delete-archivage-explicite"></a>
|
|
|
|
## 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 d'audit, restauration ou conformité.
|
|
- Quand l'utiliser : dès qu'une 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)
|
|
|
|
```txt
|
|
- 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é
|
|
|
|
---
|
|
|
|
<a id="pattern-pagination-robuste-cursor-based"></a>
|
|
|
|
## 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 l'utiliser : dès qu'un listing peut dépasser quelques dizaines/centaines d'items ou subir des écritures concurrentes.
|
|
- Quand l'éviter : listes strictement petites et statiques.
|
|
- Avantage :
|
|
- Résultats stables malgré insertions/suppressions
|
|
- Meilleure performance que l'offset 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)
|
|
|
|
```txt
|
|
- 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
|
|
|
|
---
|
|
|
|
<a id="pattern-idempotency-key-operations-sensibles"></a>
|
|
|
|
## 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 l'utiliser : 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)
|
|
|
|
```txt
|
|
- Client fournit Idempotency-Key
|
|
- Backend stocke la clé + résultat
|
|
- Retry retourne le résultat initial
|
|
```
|
|
|
|
### Checklist
|
|
|
|
- Clé obligatoire sur endpoints sensibles
|
|
- Contrainte d'unicité côté DB
|
|
- Comportement documenté
|
|
|
|
---
|
|
|
|
<a id="pattern-prisma-p2002-update-unique"></a>
|
|
|
|
## Pattern : mapping explicite de `P2002` Prisma sur create/update de champ unique
|
|
|
|
- Objectif : transformer un conflit d'unicité prévisible en erreur métier exploitable plutôt qu'en 500 opaque.
|
|
- Contexte : `create`, `update` ou `upsert` Prisma sur un champ `@unique` alimenté par une source externe, concurrente, ou après un pre-check.
|
|
- Quand l'utiliser : dès qu'un champ unique peut entrer en collision — à la création ET à la modification.
|
|
- Quand l'éviter : jamais si le champ peut réellement entrer en collision.
|
|
- Avantage :
|
|
- réponse client stable
|
|
- diagnostic métier plus rapide
|
|
- Limites / vigilance :
|
|
- le mapping doit rester cohérent avec le format d'erreur API standardisé
|
|
- Validé le : 10-03-2026
|
|
- Contexte technique : Prisma / PostgreSQL / NestJS
|
|
|
|
### Implémentation (exemple minimal)
|
|
|
|
```txt
|
|
- Catch explicite de PrismaClientKnownRequestError code P2002
|
|
- Mapping vers une erreur métier stable
|
|
- Conserver requestId et format d'erreur standardisé
|
|
```
|
|
|
|
### Implémentation (exemple complet)
|
|
|
|
```typescript
|
|
import { Prisma } from "@prisma/client";
|
|
|
|
try {
|
|
await prisma.item.create({ data: { ... } });
|
|
// ou: await prisma.item.update({ where: { id }, data: { ... } });
|
|
} catch (err) {
|
|
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
|
|
throw new HttpError("Un élément avec ce nom existe déjà.", { status: 409 });
|
|
}
|
|
throw err;
|
|
}
|
|
```
|
|
|
|
**Important :** un pre-check applicatif (`findUnique` avant `create`) ne suffit pas contre les race conditions. Le `try/catch P2002` est le seul garde-fou fiable. S'applique à `create`, `update`, `updateMany`, `upsert`.
|
|
|
|
### Checklist
|
|
|
|
- `P2002` intercepté sur les creates ET les updates sensibles
|
|
- Code d'erreur métier stable (409 Conflict)
|
|
- Pas de 500 générique sur conflit prévisible
|
|
|
|
---
|
|
|
|
<a id="pattern-decimal-prisma-serialisation"></a>
|
|
## Pattern : Sérialiser les champs `Decimal` Prisma en string au niveau du repository
|
|
|
|
- Objectif : éviter que les objets `Decimal` Prisma traversent les couches et causent des erreurs de sérialisation JSON silencieuses.
|
|
- Contexte : tout champ `Decimal` en Prisma (ex: `price`) retourné via API ou Server Action.
|
|
- Quand l'utiliser : systématiquement sur tout champ `Decimal` dans les repositories.
|
|
- Risque si ignoré : `Decimal` n'est pas JSON-sérialisable nativement — comportement varie selon Node vs browser vs test runner.
|
|
- Validé le : 17-03-2026
|
|
- Contexte technique : Prisma / Node.js — app-template-resto
|
|
|
|
### Implémentation
|
|
|
|
```typescript
|
|
// Repository — convertir avant de retourner
|
|
return {
|
|
...dish,
|
|
price: dish.price?.toString() ?? null, // Decimal → string
|
|
};
|
|
|
|
// DTO public
|
|
type DishDto = {
|
|
price: string | null; // pas Decimal
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
<a id="pattern-prisma-migration-manuelle-p3014"></a>
|
|
## Pattern : Prisma — Migration manuelle sans shadow DB (P3014)
|
|
|
|
- Objectif : créer et appliquer une migration Prisma quand la shadow database est interdite (DB managée, permissions restreintes).
|
|
- Contexte : DB managées — Supabase, PlanetScale, Railway avec rôle limité, RDS sans superuser.
|
|
- Quand l'utiliser : quand `prisma migrate dev` échoue avec `P3014 Prisma Migrate could not create the shadow database`.
|
|
- Risque si ignoré : blocage complet de la migration sur env managé.
|
|
- Validé le : 23-03-2026
|
|
- Contexte technique : Prisma v7+ — app-alexandrie / Supabase
|
|
|
|
### Implémentation
|
|
|
|
```bash
|
|
# 1. Écrire le SQL manuellement
|
|
mkdir -p prisma/migrations/<timestamp>_<nom>
|
|
# Créer migration.sql à la main
|
|
|
|
# 2. Appliquer le SQL directement en DB
|
|
npx prisma db execute --file prisma/migrations/<timestamp>_<nom>/migration.sql
|
|
|
|
# 3. Marquer la migration comme appliquée dans _prisma_migrations
|
|
npx prisma migrate resolve --applied <timestamp>_<nom>
|
|
|
|
# Note Prisma v7 : ne pas utiliser --schema= (option supprimée), utiliser prisma.config.ts
|
|
```
|
|
|
|
**Ne pas utiliser `prisma db push` en production** — il ne versionne pas les migrations.
|
|
|
|
---
|
|
|
|
<a id="pattern-filtrage-metier-service"></a>
|
|
## Pattern : Filtrage des règles métier dans le service, pas dans le repository
|
|
|
|
- Objectif : séparer la couche d'accès aux données (repository) des règles de visibilité métier (service).
|
|
- Contexte : entités publiques avec règles de filtrage (`isVisible`, `isActive`), qui varient selon le contexte appelant (public vs admin).
|
|
- Quand l'utiliser : dès qu'une règle de visibilité dépend du contexte d'appel.
|
|
- Quand l'éviter : filtres de performance (pagination, tenant scoping) — ceux-là restent dans le `where`.
|
|
- Avantage :
|
|
- la règle est testable unitairement sans Prisma (mock de données brutes)
|
|
- la requête DB reste simple et stable entre contextes
|
|
- les cas futurs (ex: admin voit les invisibles) ne nécessitent pas de modifier la requête
|
|
- Validé le : 17-03-2026
|
|
- Contexte technique : Prisma / Node.js / Next.js — app-template-resto
|
|
|
|
### Implémentation (exemple minimal)
|
|
|
|
```typescript
|
|
// Repository — charge tout ce qui est candidat
|
|
async findCategories(tenantId: string) {
|
|
return prisma.category.findMany({ where: { tenantId } }); // pas de filtre isVisible
|
|
}
|
|
|
|
// Service — applique la règle métier et mappe vers DTO
|
|
const raw = await repo.findCategories(tenantId);
|
|
return raw.filter(c => c.isVisible).map(toPublicDto);
|
|
|
|
// Admin : même repo, filtre différent dans le service admin
|
|
return raw.map(toAdminDto); // retourne tout, visible ou non
|
|
```
|