🧠 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=true
dit à 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_version
permet 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 |