🧠 Concept : Versioned schema contracts pour modèles incremental
Quand tu fais du materialized='incremental', le modèle dbt :
- ajoute uniquement les nouvelles lignes à une table existante
- doit garder le même schéma entre les runs successifs
- est vulnérable aux changements inattendus de colonnes (ex : ajout, suppression, renommage)
👉 Pour sécuriser ce processus, on applique une stratégie appelée “contrats de schéma versionnés” :
on verrouille le schéma et on contrôle explicitement ses évolutions, version après version.
✅ Étapes valides pour gérer des contrats de schéma versionnés
1. Définir un modèle incrémental avec contrat de schéma
-- models/staging/products/stg_products_v1.sql
{{ config(
materialized='incremental',
schema_contract=true,
contract={"enforced": true}
) }}
SELECT
id AS product_id,
name,
price
FROM {{ source('api', 'products') }}
✅ Ici :
schema_contract=truedit à dbt : “un contrat existe”contract={"enforced": true}oblige dbt à appliquer exactement les colonnes du SELECT- Pas de colonnes définies ici : elles seront déclarées dans le fichier
schema.yml(cf. étape suivante)
2. Déclarer les colonnes dans le fichier schema.yml
# models/staging/products/schema.yml
version: 2
models:
- name: stg_products_v1
description: "Modèle produit version 1"
meta:
schema_version: "v1"
columns:
- name: product_id
data_type: string
tests: [not_null]
- name: name
data_type: string
- name: price
data_type: float
✅ Ce fichier :
- déclare les colonnes officiellement attendues
- leur type (
data_type) est utilisé pour validation sienforced: true - le champ
meta.schema_versionpermet de versionner proprement le modèle
3. Migrer vers une nouvelle version du modèle
Imaginons que tu ajoutes une colonne category.
Tu ne modifies pas directement le modèle v1. Tu crées une nouvelle version :
-- models/staging/products/stg_products_v2.sql
{{ config(
materialized='incremental',
schema_contract=true,
contract={"enforced": true}
) }}
SELECT
id AS product_id,
name,
price,
category
FROM {{ source('api', 'products') }}
Et dans schema.yml :
- name: stg_products_v2
description: "Modèle produit version 2 avec colonne category"
meta:
schema_version: "v2"
columns:
- name: product_id
data_type: string
- name: name
data_type: string
- name: price
data_type: float
- name: category
data_type: string
🔁 Résultat : ce que tu obtiens
| Avantage | Détail |
|---|---|
| ✅ Contrôle strict | dbt refusera toute colonne non déclarée |
| ✅ Séparation des versions | v1 et v2 coexistent, rien ne casse |
| ✅ Audit clair | Tu sais qui a modifié quoi, à quelle version |
| ✅ Migration maîtrisée | Tu peux tester v2 avant de remplacer v1 |
| ✅ Compatible CI/CD | Chaque version a ses propres tests |
🧪 Bonus : test de cohérence entre versions
Tu peux créer un modèle temporaire pour comparer les deux versions :
-- models/audit/compare_v1_v2.sql
SELECT 'product_count' AS metric, COUNT(*) AS v1_count
FROM {{ ref('stg_products_v1') }}
UNION ALL
SELECT 'product_count', COUNT(*)
FROM {{ ref('stg_products_v2') }}
Ou ajouter des tests de non-régression personnalisés avec dbt assertions.
✅ En résumé
| Élément dbt | Utilisé ? | Où ? |
|---|---|---|
schema_contract=true | ✅ | dans config() |
contract={"enforced": true} | ✅ | dans config() |
| Colonnes et types | ✅ | dans schema.yml |
| Numéro de version | ✅ | via meta.schema_version ou dans le nom du modèle |
Attributs fictifs (columns dans config) | ❌ | jamais utilisés ✅ |
🧪Bonus 2
⚙️ 4. Orchestration avec selectors.yml
Tu crées un fichier selectors.yml à la racine du projet :
selectors:
- name: version_v1
description: "Exécute tous les modèles de la version 1"
definition:
method: path
value: models/staging/products/stg_products_v1.sql
- name: version_v2
description: "Exécute tous les modèles de la version 2"
definition:
method: path
value: models/staging/products/stg_products_v2.sql
Ou mieux : via des tags
Ajoute un tag dans chaque modèle :
{{ config(
materialized='incremental',
schema_contract=true,
contract={"enforced": true},
tags=['products', 'v1']
) }}
Et le fichier selectors.yml devient :
selectors:
- name: version_v1
definition:
method: tag
value: v1
- name: version_v2
definition:
method: tag
value: v2
🚀 5. Exécution conditionnelle dans ton pipeline
# En prod stable :
dbt run --selector version_v1
# En test QA :
dbt run --selector version_v2
Tu peux donc :
- exécuter uniquement la version stable (
v1) - tester la version future (
v2) sans impacter la prod - faire une bascule maîtrisée plus tard
✅ Résumé de l’approche complète
| Étape | Élément dbt | Description |
|---|---|---|
| ✅ Schéma verrouillé | schema_contract=true + enforced: true dans le modèle | |
| ✅ Colonnes définies | Dans schema.yml, avec data_type | |
| ✅ Versions distinctes | Nommage (_v1, _v2) ou meta.schema_version | |
| ✅ Exécution ciblée | Avec selectors.yml basé sur tag ou path |