mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-06 21:41:42 +02:00
139 lines
5.7 KiB
Markdown
139 lines
5.7 KiB
Markdown
# Backend — Patterns : Contracts
|
|
|
|
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet.
|
|
|
|
---
|
|
|
|
<a id="pattern-contracts-first-zod-infer-no-dto"></a>
|
|
|
|
## Pattern : Contracts-First / Zod-Infer / No-DTO (monorepo TypeScript fullstack)
|
|
|
|
- Objectif : avoir une seule source de vérité pour les contrats d'interface 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 l'utiliser : dès qu'une 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)
|
|
|
|
```typescript
|
|
// 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 d'erreur 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 d'erreur applicatifs stables
|
|
- Enums et constantes partagées (ex : liste officielle de sujets/topics)
|
|
|
|
### Ce qui n'appartient PAS à contracts
|
|
|
|
- Logique métier
|
|
- Modules/services/guards framework (NestJS, etc.)
|
|
- State management client (Zustand, Redux, etc.)
|
|
|
|
### Checklist
|
|
|
|
- [ ] Zéro DTO manuel dans l'API — uniquement `z.infer<typeof Schema>`
|
|
- [ ] `ZodValidationPipe` global ou par endpoint pour la validation d'entrée
|
|
- [ ] Constantes partagées (enums, listes) dans contracts, jamais dupliquées
|
|
- [ ] Mocks de tests importent les types depuis contracts
|
|
|
|
---
|
|
|
|
<a id="pattern-contracts-error-codes"></a>
|
|
|
|
## Pattern : Contracts-First — error codes comme contrat obligatoire
|
|
|
|
- Objectif : maintenir les codes d'erreur 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 d'importer l'enum → 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 l'enum, pas une string littérale
|
|
- [ ] PR review : vérifier `error-code.ts` à chaque ajout d'endpoint d'erreur
|
|
|
|
---
|
|
|
|
<a id="pattern-http-200-payload-metier"></a>
|
|
## Pattern : Réponse HTTP 200 avec payload métier pour les états d'accès
|
|
|
|
- Objectif : éviter les codes 4xx pour des états métier normaux qui nécessitent un rendu côté client.
|
|
- Contexte : endpoints dont la réponse varie selon les droits ou l'état d'abonnement, sans que l'absence de contenu soit une erreur.
|
|
- Quand l'utiliser : paywall, trial read-only, quota soft, état d'accès partiel — quand le client doit décider du rendu.
|
|
- Quand l'éviter : accès réellement interdit côté serveur (403), non authentifié (401), endpoint inexistant (404).
|
|
- Avantage :
|
|
- pas de gestion d'exception côté client mobile pour des états courants
|
|
- rendu conditionnel (paywall, teaser, empty) piloté par le payload
|
|
- log serveur propre — 4xx réservés aux erreurs techniques/sécurité
|
|
- Limites / vigilance :
|
|
- ne pas généraliser aux vraies erreurs de sécurité — 401/403/404 gardent leur sémantique HTTP
|
|
- Validé le : 20-03-2026
|
|
- Contexte technique : NestJS / Expo React Native — app-alexandrie story 4.1
|
|
|
|
### Implémentation (exemple minimal)
|
|
|
|
```typescript
|
|
// GET /community/forums
|
|
// Sans abonnement → 200 + { data: { forums: [], paywallRequired: true }, meta }
|
|
// Avec abonnement → 200 + { data: { forums: [...], paywallRequired: false }, meta }
|
|
|
|
// ❌ Anti-pattern
|
|
return res.status(402).json({ error: { code: 'SUBSCRIPTION_REQUIRED' } });
|
|
|
|
// ✅ Pattern correct
|
|
return res.status(200).json({
|
|
data: { forums: [], paywallRequired: true },
|
|
meta: { total: 0 },
|
|
});
|
|
```
|
|
|
|
### Règle
|
|
|
|
- **4xx** = erreur technique ou de sécurité (401 non authentifié, 403 accès interdit, 404 introuvable)
|
|
- **200 + flag métier** = état métier normal que le client doit interpréter pour le rendu
|