🧩 Schemas — Guide PRO (Web & Dev)
“Schema” = contrat + structure + règles : DB schema, JSON Schema, OpenAPI, GraphQL schema, Avro/Proto, schema.org (SEO), config schemas, migrations, versioning, validation & governance.
Concept & vocabulaire
schema vs data, contract, constraints
FondationsDB Schema (SQL)
tables, keys, constraints, namespace
DBProdORM & Migrations
model schema, migrations, drift, rollback
ORMJSON Schema
validation, required, formats, $ref
ValidationOpenAPI / Swagger
contract API REST, codegen, mocks
APIGraphQL Schema
types, queries, mutations, federation
GraphQLAvro / Protobuf
schemas event-driven, evolution rules
EventsSchema.org (SEO)
JSON-LD, rich snippets, entities
SEOConfig Schemas
YAML/JSON validation, policy, IaC
OpsVersioning & compat
backward/forward, semver, deprecation
GouvernancePipeline de validation
request → validate → persist → publish
QualitéOutils & patterns
lint, tests de contrat, mocks, CI
Tooling- Schema = description formelle de la forme des données + contraintes (types, required, invariants).
- But : réduire l’ambiguïté FE/BE/DB/events et rendre les erreurs détectables tôt (shift-left).
- Un schema “utile” répond à : Quoi (structure), Comment (règles), Quand (versions), Qui (owner).
- Data = instances (les valeurs réelles).
- Schema = la règle qui dit si une instance est valide.
- Si ton schema n’est pas testé et pas enforced, c’est juste de la doc → drift.
| Terme | Définition courte | Exemple | Erreur typique |
|---|---|---|---|
| Contract | “Ce que j’accepte / ce que je renvoie” | API request/response | Doc ≠ réalité |
| Validation | Vérifier forme + règles | JSON Schema / OpenAPI | Validation tardive → 500 |
| Invariant | Règle toujours vraie | email unique, age ≥ 18 | Règle uniquement en code |
| Backward compatible | Anciens consumers continuent | ajouter champ optional | renommer champ |
| Forward compatible | Nouveaux consumers lisent ancien | defaults/optional | suppression destructrice |
| Breaking change | Casse des clients | string → int | pas de version/deprecation |
| Schema registry | Référentiel central + règles | Avro/Proto sur Kafka | schemas “dans les apps” |
| Drift | Divergence entre sources | DB ≠ migrations ≠ code | modifs manuelles prod |
| Besoin | Schema recommandé | Pourquoi |
|---|---|---|
| API REST | OpenAPI + JSON Schema | contrat + doc + codegen |
| Events (Kafka/PubSub) | Avro/Protobuf + registry | evolution + compat |
| DB | Constraints SQL | invariants au plus près des données |
| GraphQL | SDL | contrat central, introspection |
| SEO | schema.org JSON-LD | rich snippets, compréhension bots |
| Changement | Souvent safe | Souvent breaking |
|---|---|---|
| API | Ajouter champ optional / endpoint | Supprimer/renommer champ, changer type |
| DB | Ajouter colonne nullable | NOT NULL sans backfill, drop colonne |
| Events | Ajouter champ avec default | Renommer / réutiliser un field id (Proto) |
Client
|
v
API (OpenAPI/JSON Schema)
| (1) Validate input -> 400/422 (details)
v
Service (business rules + auth)
|
| (2) Persist with DB constraints -> 409/constraint mapped
v
Database (PK/FK/UNIQUE/CHECK)
|
| (3) Publish event with schema -> registry compat gate
v
Message Broker (Avro/Proto)
|
v
Consumers (validate/parse) -> storage/analyticsProducer owns schema:
- API team owns OpenAPI
- DB team owns DB constraints
- Event producer owns message schema
Consumers:
- can request changes
- must be notified for breaking/deprecationUn même objet “User” peut avoir plusieurs représentations selon la couche : API DB Event SEO
| Couche | Schema | Exemple de règle | Pourquoi |
|---|---|---|---|
| API | OpenAPI/JSON Schema | email required + format | contrat client |
| DB | Constraints SQL | email UNIQUE, NOT NULL | invariant stockage |
| Event | Avro/Proto | field ids stables | compat consumers |
| SEO | schema.org | Organization/Person markup | compréhension bots |
- Front envoie age: "18" (string) → backend attend int → bug tardif
- Un consumer Kafka casse car un champ est renommé sans compat
- DB accepte des lignes invalides car aucune contrainte
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "User",
"type": "object",
"additionalProperties": false,
"properties": {
"id": {"type":"integer"},
"email": {"type":"string","format":"email"},
"age": {"type":"integer","minimum":18}
},
"required": ["email"]
}openapi: 3.1.0
info: { title: Users API, version: "1.0.0" }
paths:
/users:
post:
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/UserCreate" }
responses:
"201":
description: Created
content:
application/json:
schema: { $ref: "#/components/schemas/User" }
components:
schemas:
UserCreate:
type: object
required: [email]
properties:
email: { type: string, format: email }
age: { type: integer, minimum: 18 }
User:
allOf:
- $ref: "#/components/schemas/UserCreate"
- type: object
required: [id]
properties:
id: { type: integer }type User { id: ID!, email: String!, age: Int }
input CreateUserInput { email: String!, age: Int }
type Query { user(id: ID!): User }
type Mutation { createUser(input: CreateUserInput!): User! }syntax = "proto3";
message UserCreated {
int64 id = 1;
string email = 2;
int32 age = 3;
}
// Règle: ne jamais réutiliser/renuméroter les IDs de champs.<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Ideo-Lab",
"url": "https://ideo-lab.com"
}
</script>Comme tu le souhaites : URLs listées “brutes” dans un bloc code (pratique à copier).
JSON Schema:
https://json-schema.org/
https://json-schema.org/draft/2020-12/json-schema-core.html
OpenAPI:
https://spec.openapis.org/oas/latest.html
https://swagger.io/specification/
GraphQL:
https://spec.graphql.org/
https://graphql.org/learn/schema/
Protocol Buffers:
https://protobuf.dev/
schema.org (SEO):
https://schema.org/
https://developers.google.com/search/docs/appearance/structured-data/intro
General:
https://martinfowler.com/articles/consumerDrivenContracts.html- Écrire le schema avant si multi-clients / API publique.
- CI : lint → validate fixtures → compat diff → publish artifact.
- Bloquer les breaking changes (ou forcer une version majeure).
- Valider tôt (input) → erreurs propres (400/422)
- Mapper erreurs DB (unique/fk/check) → 409 ou 422
- Sur events : registry + compat checks avant publish
- Schema décoratif : écrit mais pas testé / pas enforced.
- Tout en “any/opaque” : plus aucune protection (dette cachée).
- Breaking change silencieux : rename / type change sans versioning.
- Pas d’inventaire consumers : tu ne sais même pas qui tu casses.
- DB sans contraintes : tu repousses le problème… dans la data (c’est pire).
| KPI | Pourquoi | Cible | Signal faible |
|---|---|---|---|
| % endpoints couverts par schema | contrat explicite | ~100% | docs non alignées |
| Ratio 4xx validation / 500 | erreurs captées tôt | 4xx ↑ / 500 ↓ | bugs tardifs, exceptions non gérées |
| Breaking changes / mois | stabilité clients | ≈ 0 | hotfixes clients, rollback |
| Schema drift incidents | migrations & prod health | 0 | diff DB vs migrations |
- Contrat explicite pour chaque objet métier (types + required + invariants).
- Exemples (valides & invalides) versionnés à côté du schema.
- Versioning + politique de dépréciation (deadline claire).
- Compat gate en CI (refuse breaking changes non gérés).
- Enforcement runtime : validation à l’entrée + mapping erreurs DB.
- Ownership : qui maintient le schema, qui valide les changements.
- Observabilité : métriques sur erreurs de validation et versions en prod.
- 1) Structure : tables, colonnes, types, contraintes, index, vues.
- 2) Namespace : regroupement logique d’objets (ex: PostgreSQL public, auth, reporting).
- Le schema DB est la dernière ligne de défense : invariants au plus près des données.
- Il protège contre : bugs applicatifs, race conditions, imports foireux, scripts ad-hoc.
- Il encode un “contrat” : ce qui est possible et impossible à stocker.
- PK partout (surrogate ou naturelle, mais explicite).
- Types précis (éviter text “par défaut” si un type existe).
- Contraintes sur invariants : NOT NULL, UNIQUE, FK, CHECK.
- Index = décision de perf (basée sur requêtes).
- Isoler par domaine : auth, billing, analytics
- Gérer permissions par schema + rôles.
- Réduire collisions de noms.
-- PostgreSQL
CREATE SCHEMA IF NOT EXISTS auth;
CREATE SCHEMA IF NOT EXISTS billing;
-- recherche d'objets
SHOW search_path;
SET search_path TO auth, public;| Objet | Convention | Exemple | Pourquoi |
|---|---|---|---|
| Tables | snake_case, pluriel/collectif stable | auth.users | lisible, homogène |
| PK | id | users.id | standard tooling |
| FK | <ref>_id | orders.user_id | simple et clair |
| Contraintes | noms explicites | users_email_uk | erreurs DB lisibles |
| Index | idx_* | idx_orders_user_created | diagnostic facile |
| Contrainte | Rôle | Quand l’utiliser |
|---|---|---|
| NOT NULL | champ obligatoire | invariant “toujours présent” |
| UNIQUE | unicité | email, clé métier, idempotency key |
| PRIMARY KEY | identité ligne | toujours |
| FOREIGN KEY | référentiel | relations, éviter orphelins |
| CHECK | règle locale | age >= 18, status in (...) |
- RESTRICT/NO ACTION : empêche suppression si enfants (souvent par défaut).
- CASCADE : supprime enfants automatiquement (attention prod).
- SET NULL : garde enfants mais coupe le lien.
-- Exemple FK (PostgreSQL)
ALTER TABLE orders
ADD CONSTRAINT orders_user_fk
FOREIGN KEY (user_id) REFERENCES auth.users(id)
ON DELETE RESTRICT;| Mécanisme | + Avantages | - Risques | Quand |
|---|---|---|---|
| CHECK | simple, rapide, explicite | local à la ligne/table | invariants simples |
| FK/UNIQUE | intégrité forte | peut impacter perf si pas d’index | relations / unicité |
| Trigger | logique riche, multi-tables | complexité, debug, surprises | audit, soft delete, règles avancées |
| App code | flexible, testable | race conditions, drift | règles métier non “storage” |
- Un index accélère WHERE / JOIN / ORDER BY… mais coûte en INSERT/UPDATE/DELETE.
- Index ≠ contrainte (mais souvent liés : UNIQUE → index unique).
- Principe : indexer selon les requêtes réelles (et pas “au feeling”).
- Ordre des colonnes = important : colonne la plus sélective / utilisée en filtre d’abord.
- Ex : (user_id, created_at DESC) pour “orders d’un user triées par date”.
- “Covering index” (selon DB) pour éviter des reads table (index-only scans).
-- PostgreSQL: index composite + tri
CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at DESC);
-- index partiel (seulement les lignes utiles)
CREATE INDEX idx_orders_open
ON orders(user_id, created_at DESC)
WHERE status = 'OPEN';| Besoin | Solution | Note |
|---|---|---|
| Unicité email | UNIQUE(email) | implique un index unique |
| Join rapide FK | Index sur FK | souvent indispensable |
| Filtre sur status | Index (souvent partiel) | éviter index trop large |
| Règle métier (age>=18) | CHECK | pas un index |
1) EXPAND:
- ajouter colonne nullable / nouvelle table
- ajouter index en mode non bloquant (si possible)
2) BACKFILL:
- remplir progressivement (batchs) + monitoring
3) CONTRACT:
- NOT NULL, contraintes finales
- switch code/queries
- supprimer ancien champ après fenêtre-- (1) colonne existe en NULL
ALTER TABLE users ADD COLUMN country text;
-- (2) backfill (en batch côté app/cron)
-- UPDATE users SET country='FR' WHERE country IS NULL LIMIT ...
-- (3) contrainte finale
ALTER TABLE users ALTER COLUMN country SET NOT NULL;- Tu dois pouvoir reconstruire la DB “from scratch” via migrations (CI).
- Évite modifications manuelles en prod (ou alors : scriptées + commit dans repo).
- Ajoute des checks automatiques : diff schema prod vs attendu.
- Least privilege : l’app n’a pas les droits “DBA”.
- Rôles par usage : read-only, read-write, migrations.
- Isoler les schemas sensibles (auth, secrets).
-- rôles
CREATE ROLE app_ro NOINHERIT;
CREATE ROLE app_rw NOINHERIT;
-- droits
GRANT USAGE ON SCHEMA auth TO app_ro, app_rw;
GRANT SELECT ON ALL TABLES IN SCHEMA auth TO app_ro;
GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA auth TO app_rw;
-- default privileges (pour nouvelles tables)
ALTER DEFAULT PRIVILEGES IN SCHEMA auth
GRANT SELECT ON TABLES TO app_ro;Application Layer
- business rules (complex)
- validations (shape)
|
v
Database Layer (hard guarantees)
- PK / FK / UNIQUE / CHECK => integrity
- types => meaning
- indexes => performance contract
- grants => security boundariesMore constraints -> more correctness (usually good)
More indexes -> faster reads (but slower writes)
Goal:
keep constraints strong
keep indexes purposeful (query-driven)auth.users (id PK, email UNIQUE, ...)
1
|
| FK orders.user_id -> users.id
|
orders (id PK, user_id FK, created_at, status, ...)
Index:
idx_orders_user_created (user_id, created_at DESC)
Partial:
idx_orders_open WHERE status='OPEN'CREATE SCHEMA IF NOT EXISTS auth;
CREATE TABLE auth.users (
id bigserial PRIMARY KEY,
email text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
age int CHECK (age IS NULL OR age >= 18),
CONSTRAINT users_email_uk UNIQUE(email)
);
CREATE TABLE public.orders (
id bigserial PRIMARY KEY,
user_id bigint NOT NULL,
status text NOT NULL CHECK (status IN ('OPEN','PAID','CANCELLED')),
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT orders_user_fk FOREIGN KEY(user_id) REFERENCES auth.users(id)
);
CREATE INDEX idx_orders_user_created ON public.orders(user_id, created_at DESC);
CREATE INDEX idx_orders_open ON public.orders(user_id, created_at DESC) WHERE status='OPEN';-- Oracle (extrait)
CREATE TABLE users (
id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
email VARCHAR2(320) NOT NULL,
created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
age NUMBER,
CONSTRAINT users_email_uk UNIQUE(email),
CONSTRAINT users_age_ck CHECK (age IS NULL OR age >= 18)
);
CREATE TABLE orders (
id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id NUMBER NOT NULL,
status VARCHAR2(20) NOT NULL,
created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
CONSTRAINT orders_user_fk FOREIGN KEY(user_id) REFERENCES users(id),
CONSTRAINT orders_status_ck CHECK (status IN ('OPEN','PAID','CANCELLED'))
);
CREATE INDEX idx_orders_user_created ON orders(user_id, created_at);-- contraintes
SELECT conname, contype, conrelid::regclass AS table_name
FROM pg_constraint
WHERE connamespace = 'auth'::regnamespace
ORDER BY table_name, conname;
-- index d'une table
SELECT indexname, indexdef
FROM pg_indexes
WHERE schemaname='public' AND tablename='orders';SELECT table_schema, table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema IN ('auth','public')
AND column_name IN ('email','created_at','user_id')
ORDER BY table_schema, table_name;SELECT constraint_name, constraint_type, status
FROM user_constraints
WHERE table_name = 'USERS'
ORDER BY constraint_type, constraint_name;URLs en bloc code (copiable), comme tu l’aimes.
PostgreSQL:
https://www.postgresql.org/docs/current/ddl-schemas.html
https://www.postgresql.org/docs/current/ddl-constraints.html
https://www.postgresql.org/docs/current/indexes.html
https://www.postgresql.org/docs/current/sql-createindex.html
Oracle:
https://docs.oracle.com/en/database/
(chercher: "Oracle Database constraints", "CREATE TABLE", "CREATE INDEX")
General (design):
https://use-the-index-luke.com/- PK partout + FKs là où la relation est réelle (sinon justifier).
- UNIQUE pour clés métier (email, slug, idempotency key).
- CHECK pour règles simples (enum, bornes, formats simples).
- Nommer les contraintes → erreurs DB lisibles + debugging plus rapide.
- Index selon requêtes (profiling), pas “par défaut”.
- Index sur colonnes FK souvent nécessaire.
- Migrations : expand/backfill/contract.
- Rôles séparés (runtime vs migrations) + least privilege.
- Tout nullable + aucune contrainte → incohérences de data assurées.
- “Pas de FK pour la perf” sans stratégie alternative → orphelins, deletes dangereux.
- Types fourre-tout (text/json) sans validation → dette de données.
- Index partout → writes lents + bloat + maintenance pénible.
- Migrations destructrices sans plan (drop/rename direct) → downtime/rollback.
- Rôle applicatif trop puissant (DROP/ALTER) → catastrophe si compromis.
| KPI | Cible | Pourquoi | Action |
|---|---|---|---|
| Tables sans PK | 0 | intégrité + perf joins | ajouter PK |
| Colonnes critiques NULLables | 0 | invariants | backfill + NOT NULL |
| Violations UNIQUE/FK | 0 | cohérence | clean data + constraints |
| Index inutilisés (long terme) | faible | write overhead | review / drop |
| Migrations “long lock” | 0 | stabilité prod | online pattern |
- PK sur chaque table.
- FK là où relation réelle + stratégie ON DELETE explicitée.
- UNIQUE sur clés métier (email/slug/idempotency key).
- CHECK pour invariants simples (enum, bornes, règles locales).
- Index selon requêtes + index sur FKs si nécessaire.
- Naming clair (constraints/index) pour diagnostic rapide.
- Migrations safe (expand/backfill/contract), surtout sur grosses tables.
- Rôles séparés (runtime vs migrations) + least privilege.
- Audit : détecter drift (prod vs repo) + monitoring erreurs contraintes.
- Un ORM (Django/SQLAlchemy/etc.) a son propre “schema” : models + migrations.
- Risque : drift (DB réelle ≠ migrations ≠ code).
- But : garder un seul source of truth et des déploiements reproductibles.
# Exemple mental (peu importe le framework)
Model User:
email: string unique not null
created_at: datetime default now
Migration:
add column / add constraint / add index
Drift typique:
prod a une colonne en plus ou une contrainte manquante- CI : appliquer migrations sur une DB “from scratch” + exécuter tests.
- Pour prod : migration safe (add column nullable → backfill → set not null).
- “Expand/Contract pattern” pour changements lourds (renommage, split table, etc.).
- Monitoring : détecter les migrations lentes/verrouillantes.
- Modifier la DB à la main en prod puis “oublier” de créer la migration.
- Migration qui fait data + schema sans stratégie (rollback impossible).
- Renommage champ = breaking change si clients / ETL / BI existent.
- Migrations linéaires et reproductibles.
- Stratégie rollback (ou forward-only documenté).
- Validation “schema drift” automatisée (CI + check prod).
- Changements “online” pour tables volumineuses.
- JSON Schema décrit la forme d’un JSON : types, required, enum, formats, min/max, regex, etc.
- Usage : validation d’inputs (API), config, documents, storage JSON, contracts inter-services.
- Force : validation automatique + réutilisation via
$ref.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"properties": {
"id": {"type":"integer"},
"email": {"type":"string","format":"email"},
"age": {"type":"integer","minimum":18}
},
"required": ["id","email"]
}# points clés
- required = champs obligatoires
- additionalProperties=false = pas de champs “surprise”
- format=email = validation sémantique minimale- Verrouiller l’objet :
additionalProperties=false(sinon dérive silencieuse). - Factoriser via
$ref(User, Money, Address…). - Avoir des fixtures “valid/invalid” et tester en CI.
- Générer documentation + exemples automatiquement.
- “type: object” sans properties → useless.
- Tout en
anyOfcomplexes → schema incompréhensible. - Formats non testés (ou faux sentiment de sécurité).
- Types + required définis.
- Règles métier simples intégrées (min/max/enum).
- Interdiction de champs inconnus si besoin.
- Tests de validation en CI.
- OpenAPI décrit une API REST : endpoints, params, auth, request/response schemas, erreurs.
- Valeur : doc vivante + codegen + mocks + contract tests.
openapi: 3.1.0
info: { title: Users API, version: "1.0.0" }
paths:
/users/{id}:
get:
parameters:
- in: path
name: id
required: true
schema: { type: integer }
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/User"
components:
schemas:
User:
type: object
required: [id, email]
properties:
id: { type: integer }
email: { type: string, format: email }- Définir des réponses d’erreur standard (
400/401/403/404/409/422) avec schema commun. - Mettre des examples request/response (très important pour les clients).
- Bloquer les breaking changes via tooling (diff de spec en CI).
- Contract tests : serveur conforme à la spec + clients générés compilent.
- Spec écrite “après” et jamais synchronisée → faux contrat.
- “schema: {}” partout → doc inutile.
- Changements sans versionning/deprecation → chaos côté clients.
- Chaque endpoint a params + réponses typées.
- Auth documentée (bearer, api key, oauth…).
- Erreurs standardisées avec schema.
- Spec versionnée + diff check en CI.
- GraphQL a un schema central (SDL) : types, champs, queries/mutations, inputs, enums.
- Contrat puissant : le client sait exactement ce qu’il peut demander.
- Attention : la perf se gère (N+1, depth/complexity, caching).
type User { id: ID!, email: String!, age: Int }
type Query { user(id: ID!): User }
input CreateUserInput { email: String!, age: Int }
type Mutation { createUser(input: CreateUserInput!): User! }- Limiter la complexité : depth/complexity limits + timeouts.
- Résoudre N+1 (DataLoader pattern, batching).
- Déprécier proprement :
@deprecated(reason: "...")+ fenêtre de migration. - Registry/federation si multi-services.
- Schémas trop “DB-like” (exposer tables internes) → couplage fort.
- Aucune limite → requêtes monstrueuses / DoS applicatif.
- Champs qui changent de sens sans versioning → clients cassés.
- Schema SDL versionné.
- Déprecations tracées.
- Limits + protections perf.
- Tests de contrat (introspection + snapshot).
- Pour l’event-driven (Kafka, Pub/Sub, RabbitMQ), le schema devient vital.
- Protobuf : contrats stricts, codegen, perf (binaire), IDs de champs.
- Avro : très utilisé en data pipelines (compat rules, schema registry).
- Problème majeur : schema evolution (backward/forward/full).
// Protobuf (extrait)
message UserCreated {
int64 id = 1;
string email = 2;
int32 age = 3; // optional by presence semantics depending on syntax/version
}# Règles d’évolution (simplifiées)
- ajouter un champ : OK (si optional / default)
- supprimer un champ : dangereux (préférer "deprecated")
- renuméroter un field id : NON (breaking)- Avoir un Schema Registry (même minimal) + validation à la publication.
- Politique d’évolution : backward-only vs full-compat selon tes consommateurs.
- Déprécier avant de supprimer, et attendre la fin de fenêtre de migration.
- Tester compat en CI (ex: “new schema is backward compatible”).
- Publier des events JSON sans schema → consumers fragiles.
- Breaking change silencieux → incidents cross-services.
- Pas d’ownership → “tout le monde change tout”.
- Schema versionné + registry.
- Compat checks en CI.
- Deprecation policy claire.
- Consumer tests (canaries) sur nouveaux schémas.
- Ici “schema” = données structurées pour moteurs de recherche (souvent en JSON-LD).
- But : aider Google/Bing à comprendre une page (Article, Product, Organization, FAQ…).
- Impact : rich snippets, knowledge panels, meilleure compréhension sémantique.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Schemas en dev : guide",
"author": { "@type": "Person", "name": "Guillaume" },
"datePublished": "2026-02-12"
}
</script>- Utiliser JSON-LD (simple à injecter server-side).
- Rester fidèle au contenu visible (sinon risque de pénalité).
- Tester régulièrement (données structurées) et versionner les templates.
- Mettre du schema “fake” (FAQ inventée, reviews fausses) → mauvais plan.
- Dupliquer/injecter plusieurs JSON-LD incohérents sur la même page.
- Type schema.org cohérent (Article/Product/Org…).
- Champs essentiels présents.
- Pas de divergence contenu vs markup.
- Config schema = valider des fichiers de config (YAML/JSON) avant déploiement.
- Très utile en Ops/IaC : éviter un déploiement cassé pour une clé manquante.
- Approche : JSON Schema / validation applicative / policies (OPA) selon le niveau.
# Exemple de config "attendue"
app:
env: "prod"
port: 8080
database_url: "postgres://..."
limits:
rate_per_min: 1200
# Un schema te permet d’empêcher :
# - port string au lieu d’int
# - database_url manquant
# - env hors {dev,staging,prod}- Valider en CI (pré-merge) et au démarrage applicatif.
- Garder des defaults explicites (sinon comportement surprise).
- Documenter les “breaking config changes” (nouvelle clé requise, clé supprimée).
- Config “libre” sans validation → incidents “bêtes” en prod.
- Secrets directement dans les configs versionnées.
- Schema de config (même minimal) + validation CI.
- Defaults + required clarifiés.
- Secrets externalisés (vault/variables d’env).
- Le vrai sujet des schemas en prod, c’est l’évolution.
- Backward compatible : les anciens consumers continuent de fonctionner.
- Forward compatible : les nouveaux consumers supportent d’anciens messages.
- Breaking change : nécessite version majeure / endpoint v2 / migration plan.
# REST (typique)
- add field optional => OK
- remove field => breaking
- change type string->int => breaking
- rename field => breaking (sauf alias/deprecation)
# DB
- add column nullable => OK
- set NOT NULL sans backfill => risque (breaking en runtime)
- drop column => breaking pour ETL/BI/old code- Définir une policy deprecation : annonce + window + date de retrait.
- Prefer “additive changes” (ajout) plutôt que modifications destructives.
- Versionner les contrats : tags, releases, registry.
- Mettre un “compat gate” en CI : refuser un breaking change non versionné.
- “On change vite, les clients suivront” → non.
- Deprecation sans date → personne ne migre.
- Pas d’inventaire des consumers → impossible de savoir qui casse.
- Types & champs stables.
- Breaking change = version majeure ou endpoint v2.
- Deprecation documentée avec deadline.
- Compat checks automatisés.
- Le schema doit vivre dans le flux : entrée API → validations → DB → events → analytics.
- Idéal : les erreurs de contrat deviennent des 422/400 propres, pas des 500.
Client
|
v
API Gateway / Router
|
v
(1) Validate request schema -----> reject 400/422 (with details)
|
v
(2) Business rules + auth
|
v
(3) Persist with DB constraints ---> reject 409/constraint error (mapped)
|
v
(4) Publish event with message schema -> registry compat gate
|
v
Consumers validate / parse -> store / act- Validation “shape” (types/required) tôt, règles métier ensuite.
- Mapper proprement erreurs DB (unique/fk/check) vers erreurs API.
- Centraliser les contrats (repo “schemas” / registry) pour éviter la divergence.
- Tracer : quelles versions de schema sont utilisées en prod.
- Validation request/response (au moins request).
- DB constraints pour invariants critiques.
- Contrat event (schema evolution gérée).
- Observabilité des erreurs de validation.
- Pattern “contract-first” : spec → mocks → impl → tests → clients.
- Pattern “contract-tests” : vérifier automatiquement la conformité (producer/consumer).
- Pattern “schema registry” : un référentiel, un ownership, des règles d’évolution.
# Idées d’automatisation (CI)
- Lint schema (format + conventions)
- Validate fixtures (valid/invalid)
- Diff compat (old vs new)
- Generate docs (HTML/markdown)
- Generate clients (si besoin)
- Publish schema artifact (registry)# “Contract tests” minimalistes
Given: OpenAPI/JSON Schema
When: run tests against API
Then: responses match schemas + required fields are present- Nommer les objets : User vs UserV2 (éviter V2 trop tôt, préférer compat).
- Éviter les duplications : un schema “User” réutilisé partout via ref.
- Limiter les “any/unknown” aux zones réellement extensibles.
- Mettre des “examples” dans les schemas (accélère FE & QA).
- Docs séparées du schema → drift.
- Règles d’évolution implicites (“au feeling”).
- Aucune ownership → personne n’ose dire non aux breaking changes.
- Repo/registry des schemas + ownership.
- Compat gate en CI.
- Fixtures valid/invalid.
- Docs auto + exemples.
- Quand tu dis “schema”, précise le contexte : DB / JSON / API / GraphQL / Events / SEO.
- Le bon réflexe : contract + validation + versioning.
- JSON Schema (spec + drafts)
- OpenAPI (3.0/3.1) + tooling codegen
- GraphQL SDL + deprecations
- Protocol Buffers / Avro + schema evolution
- schema.org + JSON-LD
- Contrats centralisés + ownership.
- Validation automatisée (CI) + erreurs propres (runtime).
- Versioning & compat (deprecation policy).
SCHEMA (web/dev) = règle qui décrit la structure des données.
DB schema : tables/colonnes/types/contraintes (+ schema namespace)
JSON Schema : validation de JSON (required, types, $ref)
OpenAPI : contrat API REST (request/response/errors)
GraphQL schema : types/queries/mutations (+ deprecations)
Avro/Proto : contrat events/messages + evolution rules
schema.org : données structurées SEO (JSON-LD)SAFE (souvent) :
- ajouter un champ optional
- ajouter une colonne nullable
- ajouter un nouvel endpoint / nouveau type
BREAKING (souvent) :
- supprimer/renommer un champ
- changer type string->int
- rendre obligatoire un champ sans default/backfill
- supprimer une colonne utilisée par ETL/BI“Pour moi un schema, c’est un contrat formel :
1) il définit les types et contraintes,
2) il est validé automatiquement (CI + runtime),
3) il est versionné avec une politique de compatibilité.
Ça évite les ambiguïtés FE/BE/DB et réduit les incidents de prod.”