FAQ collaboration d'équipe : contrats, configuration et fixtures
L’essentiel : Style de nommage, formats de temps, sémantique de null, formes d'énumération, formats de configuration, organisation des fixtures et conflits de fusion — douze compromis sur lesquels les équipes se disputent réellement.

L'essentiel : tranchez ces points une fois dans une convention d'équipe — une enveloppe {"data":…,"meta":…} avec des codes de statut HTTP, un seul style de nommage sur tout le site, des horodatages RFC 3339 avec fuseau horaire, et des énumérations en chaînes — puis faites évoluer chaque nouvelle API contre le même contrat en ajoutant seulement des champs, jamais en les modifiant ou en les supprimant.
Il n'y a pas de réponse unique correcte à ces questions, mais une équipe doit avoir une réponse unique — sinon chaque nouvelle API relance le même débat.
1. Contrats d'API
Quelle structure une API doit-elle renvoyer ?
Trois formes courantes, chacune avec un coût :
| Forme | Exemple | Avantages | Inconvénients |
|---|---|---|---|
| Données brutes | [{...}, {...}] |
Simple | Ne peut pas porter de métadonnées |
| Enveloppe fixe | {"code":0,"message":"ok","data":{...}} |
Gestion d'erreur uniforme | Le code métier duplique le statut HTTP |
| Enveloppe de ressource | {"data":{...},"meta":{...},"links":{...}} |
Extensible, sémantique claire | Un niveau d'imbrication de plus |
Pour les API métier internes, préférez « enveloppe de ressource + codes de statut HTTP qui expriment le résultat » :
{
"data": [{ "id": "1", "name": "示例" }],
"meta": { "total": 128, "page": 2, "pageSize": 20 }
}
Pour les erreurs, utilisez un code de statut HTTP plus un corps d'erreur structuré au lieu d'imbriquer un autre code :
{
"error": {
"type": "validation_failed",
"message": "请求参数校验失败",
"details": [
{ "path": "/email", "reason": "格式不正确" }
]
}
}
Point clé : les codes de statut HTTP expriment déjà le succès ou l'échec, donc un code métier est redondant. Quand les deux systèmes coexistent, des incohérences comme « 201 mais code 500 » sont presque inévitables.
Évitez les tableaux nus. Avec un tableau au niveau supérieur, vous ne pouvez pas attacher pagination, totaux ou curseurs, et les ajouter plus tard signifie changer chaque appelant.
Les noms de champs doivent-ils utiliser snake_case ou camelCase ?
Choisissez-en un, appliquez-le sur tout le site, et ne les mélangez jamais dans un même document JSON.
| Style | Écosystème courant | Exemple |
|---|---|---|
camelCase |
JavaScript / TypeScript / Java | userName, createdAt |
snake_case |
Python / Ruby / Go (en partie) / colonnes de base de données | user_name, created_at |
kebab-case |
Rare ; les clés avec tirets sont malcommodes à accéder | user-name |
Critères :
- Qui consomme ce JSON : choisissez camelCase quand le frontend domine, snake_case quand les pipelines de données et les services Python dominent ;
- Est-ce mappé directement sur des colonnes de base de données : snake_case économise une couche de conversion quand on passe directement des structures de table ;
- Continuité des systèmes existants : gardez ce que l'API existante utilise ; la cohérence bat la correction.
S'il faut vraiment convertir, faites-le à la frontière (passerelle ou couche de requête frontend) plutôt que de disperser des appels toCamelCase dans le code métier.
Quel format les champs de temps doivent-ils utiliser ?
Utilisez des chaînes RFC 3339 / ISO 8601 avec un offset de fuseau horaire :
{
"createdAt": "2026-10-30T11:37:57+08:00",
"updatedAt": "2026-10-30T03:37:57Z"
}
Points clés :
- Incluez le fuseau horaire :
2026-10-30 11:37:57sans zone cassera inévitablement dans les scénarios inter-fuseaux ; - Utilisez
Zou un offset de façon cohérente : les deux sont corrects, mais ne les mélangez pas dans une même API ; - Évitez les horodatages numériques : qu'il s'agisse de secondes ou de millisecondes, et que les secondes intercalaires soient incluses, chaque équipe doit le réinterpréter. Quand la compacité est requise, utilisez un entier en millisecondes, mais épinglez l'unité dans la documentation.
Préférez des noms de champs avec un suffixe At comme createdAt / updatedAt / deletedAt,
pour qu'on voie au premier coup d'œil que la valeur est un instant plutôt qu'une durée. Pour les durées, utilisez timeoutMs, ttlSeconds — mettez l'unité dans le nom.
Les nulls doivent-ils être null ou un champ omis ?
Cela dépend si « ce champ a du sens » :
| Sémantique | Forme | Remarques |
|---|---|---|
| Le champ existe mais n'a pas encore de valeur | "name": null |
Dit explicitement « il a été vérifié et c'est vide » |
| Le champ est sans rapport avec cette ressource | Omettre | Par exemple, un utilisateur ordinaire ne devrait pas avoir vipExpireAt |
| Mise à jour partielle (PATCH) | Omettre = ne pas changer | Seul null signifie « effacer » ici |
Le troisième cas est le plus souvent négligé. Dans une requête PATCH :
{ "nickname": null }
cela signifie « effacer le pseudo », tandis qu'omettre nickname du corps de requête signifie « ne pas toucher au pseudo ».
Si vous traitez les deux comme « effacer », un utilisateur qui change son e-mail effacera son pseudo.
Encore une fois : n'utilisez pas undefined. Ce n'est pas une valeur JSON valide, et JSON.stringify supprime entièrement la clé,
donc le comportement correspond à « omettre » tandis que le code ressemble à « une valeur vide a été passée explicitement ».
Comment nommer les champs booléens ?
Utilisez les préfixes is / has / can / should et évitez les négations.
{
"isActive": true,
"hasPassword": false,
"canEdit": true
}
Trois leçons :
- Pas de négations :
isDisabled: falseoblige le lecteur à l'inverser une fois ;isEnabled: trueest plus direct. La double négation (isNotDisabled) est un désastre ; - N'utilisez pas de nombres pour les booléens :
"enabled": 1oblige chaque consommateur à écrire=== 1une fois ; - N'utilisez pas de chaînes :
"enable": "true"est vrai en JavaScript, etif (data.enable)est aussi vrai pour"false"; ces bugs sont extrêmement difficiles à tracer.
Les champs d'énumération doivent-ils utiliser des nombres ou des chaînes ?
Utilisez des chaînes sauf s'il y a une contrainte claire de stockage ou de bande passante.
{ "status": "active" }
Comparé à { "status": 1 }, les chaînes gagnent parce que :
- Les journaux, captures de paquets et bases de données sont directement lisibles sans consulter une table de correspondance ;
- Ajouter une valeur d'énumération ne change pas le sens des valeurs existantes (insérer une valeur au milieu d'une énumération numérique décale tout) ;
- Le frontend n'a pas besoin de maintenir une correspondance
1 → 'active'en synchronisation.
Le coût est de quelques octets supplémentaires, presque négligeable après compression.
Pratique associée : utilisez minuscules + underscores pour les valeurs d'énumération en chaînes (pending_review), et contraignez-les avec enum dans le Schema
pour que les valeurs invalides soient rejetées à l'étape de validation.
2. Configuration et fixtures
La configuration doit-elle être en JSON, YAML ou TOML ?
Choisissez selon l'objectif, pas selon la préférence :
| Format | Bon pour | Mauvais pour |
|---|---|---|
| JSON | Lecture et écriture programmatiques, données strictement structurées, validation de Schema | Édition manuelle fréquente (commentaires interdits) |
| YAML | Configuration de déploiement profondément imbriquée (K8s) | L'indentation écrite à la main est sujette aux erreurs ; yes/no deviennent des booléens |
| TOML | Configuration plate de dépendances et d'outils (Cargo.toml, pyproject.toml) |
Lisibilité médiocre pour l'imbrication profonde |
| JSONC | Configuration d'éditeur nécessitant des commentaires (tsconfig.json) |
Franchir les frontières système |
Une erreur courante des équipes est de stocker de la configuration métier en YAML — la conversion de type implicite de YAML (1.0 devient un nombre,
NO devient un booléen, 2026-10-30 devient une date) produit des résultats différents selon les parseurs.
Si la configuration doit être strictement validée par un programme, choisissez JSON + Schema, et documentez les commentaires via un champ _comment ou une doc séparée.
Comment organiser les fixtures de test ?
Organisez par « une base + des différences minimales », pas en copiant un énorme JSON pour chaque cas de test.
fixtures/
├─ users/
│ ├─ base.json # Baseline user object
│ ├─ no-email.json # Only email is missing
│ └─ invalid-age.json # Only age is out of range
└─ fixtures.ts # Utility functions for loading and shallow merging
import baseUser from './users/base.json';
export function makeUser(overrides: Partial<User> = {}): User {
return { ...baseUser, ...overrides };
}
Ainsi, un nouveau cas de test n'a besoin d'écrire que les champs différents, et modifier un champ de base met à jour chaque cas automatiquement.
Trois règles :
- Les fixtures doivent passer la validation de Schema : la fixture de base elle-même devrait être des données valides ;
- N'exportez pas de vraies données de production : numéros de téléphone, numéros d'identité et adresses sont tous des informations personnelles ;
- Les fixtures appartiennent au contrôle de version mais doivent rester petites : un fichier unique au-delà de quelques dizaines de Ko mérite d'être élagué ou généré.
Comment réduire les conflits de fusion dans les fichiers JSON ?
JSON entre facilement en conflit : un formateur réécrit tout le fichier d'un coup, donc deux personnes changeant chacune une ligne entrent en conflit sur tout le fichier.
Quatre tactiques :
- Unifiez le formatage et ajoutez-le à
.editorconfig: indentation, fins de ligne et retour à la ligne final cohérents évitent les diffs sans rapport ; - Triez les clés : convenez d'un ordre alphabétique pour qu'un nouveau champ n'entre pas en collision avec le point d'insertion d'un autre ;
- Découpez les fichiers : brisez un géant
config.jsonen plusieurs fichiers par domaine, et la surface de conflit se réduit naturellement ; - Évitez les commits « tout reformater » : séparez le formatage des changements métier en deux commits pour que la revue reste lisible.
Si l'équipe utilise des lock files (pnpm-lock.yaml, package-lock.json), ne les fusionnez pas à la main ; régénérez-les plutôt :
git checkout --theirs pnpm-lock.yaml
pnpm install
Comment maintenir la compatibilité de version ?
Le principe est « ajouter, ne pas modifier, ne pas supprimer ».
| Changement | Compatibilité | Approche |
|---|---|---|
| Ajouter un champ facultatif | Compatible | Ajoutez-le ; les consommateurs ignorent les champs inconnus |
| Ajouter un champ obligatoire | Incompatible | Incrémentez la version, ou fournissez un défaut |
| Supprimer un champ | Incompatible | Marquez-le d'abord déprécié, surveillez les appelants, puis supprimez |
| Changer le type d'un champ | Incompatible | Ajoutez un nouveau nom de champ et écrivez les deux pendant la transition |
| Changer l'ensemble des valeurs d'énumération | Dépend | Ajouter des valeurs est compatible ; les retirer ne l'est pas |
Mécanismes de soutien :
- Les consommateurs utilisent un « parsing indulgent » : ignorer les champs inconnus et fournir des défauts pour les manquants ;
- Le serveur garde les anciens champs un certain temps et renvoie un en-tête
Deprecationdans la réponse comme indice ; - Dans le Schema, ne marquez pas les nouveaux champs comme
required, et taguez les champs dépréciés avecdeprecated: true.
Comment valider les contrats en CI ?
Traitez le Schema comme une partie du code et validez-le à chaque commit.
# Validate sample data against the Schema (using ajv-cli)
npx ajv-cli validate -s schemas/user.json -d "fixtures/users/*.json" --strict=false
Lancez trois choses en CI :
- Validité du Schema lui-même : la déclaration
$schemaest-elle valide et$refse résout-il ; - Les fixtures correspondent-elles au Schema : garantit que les données de test ne dérivent pas ;
- Le contrat est-il rétrocompatible : comparez avec la version précédente et vérifiez les champs supprimés ou les types changés.
Le troisième élément a besoin d'un support d'outillage (comme openapi-diff ou json-schema-diff) ;
sans cela, comparez au moins les changements dans les tableaux required et les ensembles de clés properties avec un script.
3. Aide-mémoire
| Question | Recommandation |
|---|---|
| Structure de réponse | {"data":..., "meta":...}, avec des codes de statut HTTP pour les erreurs |
| Style de nommage | camelCase (frontend d'abord) ou snake_case (données d'abord), cohérent sur tout le site |
| Format de temps | Chaînes RFC 3339 avec fuseau horaire, noms de champs finissant par At |
| Nulls | Utilisez null pour pas de valeur, omettez les champs sans rapport, l'omission dans PATCH signifie « ne pas changer » |
| Nommage booléen | Préfixes is/has/can, pas de négations, pas de 0/1 |
| Énumérations | Chaînes, minuscules avec underscores, ajoutez enum dans le Schema |
| Format de configuration | JSON pour la validation programmatique, TOML ou JSONC pour l'édition manuelle |
| Fixtures | Une base + des surcharges à différence minimale, valides contre le Schema |
| Conflits de fusion | Unifier le formatage, trier les clés, découper les fichiers |
| Compatibilité de version | Ajouter, ne pas modifier, ne pas supprimer ; marquer déprécié avant de retirer |