mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-06 21:41:42 +02:00
Refonte Structure
This commit is contained in:
138
knowledge/backend/patterns/contracts.md
Normal file
138
knowledge/backend/patterns/contracts.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user