RSS →
ARTICLES · DATA CONTRACT

Data contracts : poser un contrat entre équipes productrices et consommatrices

Il y a quelques semaines, l'équipe marketing m'a appelé en panique : leur tableau de bord indiquait 2,4 millions d'euros de chiffre d'affaires au lieu de 24 millions. Pas un bug d'affichage. Un champ amount avait changé de sémantique côté source, personne ne l'avait vu, et le rapport avait tourné deux jours sur des données fausses avant qu'un analyste ne remarque l'anomalie. Pas de test, pas de contrat, pas de propriétaire identifié. C'est exactement le scénario que les data contracts éliminent. Voici comment je les mets en place.

Data contracts

Le problème : des équipes qui se parlent par tickets

Quand plusieurs équipes consomment les données produites par d'autres, tout finit par reposer sur des hypothèses implicites. Le producteur change le sens d'une colonne, supprime un champ, ou modifie un format de date, et le consommateur s'en rend compte des jours plus tard, quand son rapport casse ou — pire — quand il a déjà tourné sur des données fausses.

Le pattern de communication dominant, c'est le ticket : "le champ status n'a plus les mêmes valeurs", "la table a un nouveau format de date". Un producteur qui ne sait pas exactement qui consomme quoi, des consommateurs qui n'ont aucun pouvoir sur ce que produit la table, et zéro garantie formelle. Chaque changement devient un événement à gérer dans l'urgence.

Un data contract est la réponse : un document structuré qui décrit formellement ce que le producteur s'engage à fournir (schéma, types, qualité, fraîcheur, disponibilité) et ce que le consommateur peut en attendre. Quand il est en place, un changement de schéma ne casse plus silencieusement un rapport — il est détecté au moment où le producteur veut l'appliquer.

Qu'est-ce qu'un data contract, concrètement

Il existe un standard ouvert pour ça : l'Open Data Contract Standard (ODCS), maintenu par Bitol sous licence Apache 2.0. Il définit les sections d'un contrat :

Section Contenu Exemple concret
Fundamentals Identité : domaine, data product, version, statut domain: finance, version: 1.1.0
Schema Les tables et colonnes exposées noms, types, clés, description métier
Data quality Les règles de qualité et leurs seuils taux de null, volume minimal
SLA Disponibilité, fraîcheur, latence données à jour à H-12, uptime 99.9%
Support & communication Canaux de contact, propriétaire email de l'équipe productrice
Pricing Coût d'accès aux données (data mesh) $ par octet, budget alloué
Team & Roles Qui produit, qui consomme, qui est propriétaire data owner, steward

Tout le monde n'utilise pas ODCS — certains écrivent des contrats maison. Mais partir d'un standard a un avantage énorme : un format commun et validable, qu'on peut parser, versionner et vérifier en CI. Un contrat au format propriétaire ne tient pas la route à l'échelle.

Voici un exemple réaliste de contrat ODCS pour une table métier :

# data-contracts/customer_360.odcs.yaml
apiVersion: v3.0.0
kind: DataContract
id: 5f9c1a2e-0000-4000-8000-000000000001
domain: crm
dataProduct: customer_360
status: active
version: 1.2.0

# Fundamentals
description:
  purpose: Vue unifiée client pour les équipes analytics et marketing
  limitations: Données à J-1, pas d'historique temps réel

schema:
  - name: customer_360
    physicalType: table
    properties:
      - name: customer_id
        logicalType: string
        physicalType: varchar(36)
        primaryKey: true
        required: true
        description: Identifiant unique client (UUID v4)
        classification: restricted
      - name: email
        logicalType: string
        physicalType: varchar(255)
        required: true
        description: Email principal du client
        classification: restricted
      - name: created_at
        logicalType: timestamp
        physicalType: timestamp_ntz
        required: true
        description: Date de création du compte
      - name: segment
        logicalType: string
        physicalType: varchar(20)
        required: false
        description: Segment commercial (premium, standard, basic)

quality:
  - metric: nullValues
    mustBe: 0
    dimension: completeness
    severity: error
    schedule: "0 20 * * *"
  - metric: rowCount
    mustBeGreaterThan: 100000
    dimension: completeness
    severity: warn
    schedule: "0 20 * * *"

sla:
  - freshness: 24h
    severity: error

team:
  owner: data-platform
  contact: data-platform@monorg.com

Note : le contrat ODCS décrit ce qu'on promet. Il ne garantit pas à lui seul que ces promesses sont tenues. C'est là que dbt et Snowflake entrent en jeu — ils appliquent le contrat.

Appliquer le contrat avec dbt : les model contracts

Le meilleur endroit pour commencer, c'est là où les modèles sont définis : dans dbt. La fonctionnalité model contracts de dbt transforme le contrat YAML en contrainte réellement appliquée au moment du build.

# models/marts/customer/schema.yml
version: 2

models:
  - name: customer_360
    config:
      contract:
        enforced: true
    columns:
      - name: customer_id
        data_type: varchar
      - name: email
        data_type: varchar
      - name: created_at
        data_type: timestamp_ntz
      - name: segment
        data_type: varchar

Avec contract: enforced: true, dbt exige que chaque colonne ait un name et un data_type. Et surtout : au moment du dbt build, dbt ajuste automatiquement le SQL pour que la sortie respecte exactement le contrat (projection et casting vers les types déclarés). Si une colonne du modèle ne correspond pas au contrat, le build échoue — avant même que les données ne soient exposées aux consommateurs.

C'est le point clé : le contrat n'est plus une promesse écrite dans un YAML que personne ne lit. C'est une garde qui casse le déploiement quand la réalité du modèle s'écarte de l'engagement.

Ce que dbt applique vraiment

Ce que le contrat dbt garantit Mécanisme
Colonnes exactes, dans l'ordre project + validation du nombre de colonnes
Types de données corrects cast automatique vers data_type
Échec au build en cas d'écart contract.enforced: true bloque dbt build
Nom du modèle stable le contrat est lié au name du modèle

⚠️ Le piège des types : Snowflake stocke certains types sous plusieurs alias (ex. text vs varchar). Le contrat dbt doit utiliser un data_type que Snowflake comprend réellement, et cohérent avec ce que le modèle produit. Un mauvais cast au build peut forcer une conversion implicite coûteuse sur de grosses tables.

La fraîcheur : le SLA du contrat

Le contrat promet des données à J-1. Qui vérifie que c'est tenu ? Dans dbt, la configuration freshness sur les sources transforme la promesse SLA en contrôle automatisé.

# models/staging/crm/sources.yml
version: 2

sources:
  - name: crm_prod
    database: PROD
    schema: CRM
    freshness:
      warn_after: { count: 12, period: hour }
      error_after: { count: 24, period: hour }
    tables:
      - name: customer_360
        loaded_at_field: updated_at

La commande dbt source freshness vérifie que chaque source respecte son SLA. Si une table n'a pas été mise à jour depuis plus de 24h, c'est une erreur — le pipeline s'arrête et l'alerte part, au lieu que le consommateur découvre des données périmées dans son rapport.

Et côté Snowflake ? Les grants comme contrat d'accès

Un contrat ne dit pas seulement quoi — il dit qui peut y accéder. Côté Snowflake, la traduction concrète du contrat d'accès, ce sont les grants. Le principe : donner aux consommateurs exactement l'accès promis, rien de plus.

-- L'équipe analytics consomme le modèle customer_360 (couche mart)
GRANT USAGE ON DATABASE PROD_ANALYTICS TO ROLE ANALYTICS_ROLE;
GRANT USAGE ON SCHEMA PROD_ANALYTICS.MARTS TO ROLE ANALYTICS_ROLE;
GRANT SELECT ON TABLE PROD_ANALYTICS.MARTS.CUSTOMER_360 TO ROLE ANALYTICS_ROLE;

-- Futures tables de la couche mart : le contrat s'applique aussi aux nouvelles
GRANT SELECT ON FUTURE TABLES IN SCHEMA PROD_ANALYTICS.MARTS TO ROLE ANALYTICS_ROLE;

Le FUTURE GRANT est important : il garantit que toute nouvelle table de la couche mart est automatiquement lisible par l'équipe analytics — le contrat d'accès couvre le présent et le futur, sans intervention manuelle.

Le workflow : versionner le contrat comme du code

La clé de la réussite, c'est de traiter le contrat comme un artefact de code — pas comme un document Word qu'on met à jour quand on y pense.

  1. Le contrat vit dans le repo Git de l'équipe productrice (data-contracts/).
  2. Validation en CI : on valide le YAML contre le JSON Schema d'ODCS (le standard fournit le schéma officiel, importable dans l'IDE et utilisable en CI).
  3. Version sémantique : tout changement de schéma incrémente la version du contrat (1.2.0 → 1.3.0). Un changement breaking (colonne supprimée, type modifié) impose une majeure et notifie les consommateurs.
  4. dbt applique : le schema.yml de dbt reflète le contrat, avec contract.enforced et la freshness.
  5. Revue de changement : avant de merger un changement de schéma, on identifie les consommateurs référencés dans le contrat et on les informe.
# .gitlab-ci.yml — validation du contrat en CI
validate-contracts:
  script:
    - python validate_odcs.py data-contracts/   # valide vs JSON Schema ODCS
    - dbt parse                                 # valide les schémas dbt
    - dbt build                                 # applique contract.enforced + freshness

Le pipeline CI est le juge de paix : si le contrat casse, le merge est bloqué. Le consommateur n'apprend plus un changement de schéma en découvrant un rapport cassé — il est informé en amont, au moment du merge.

Les erreurs que j'ai faites pour vous

ERREUR CONSÉQUENCE CORRECTION
Écrire un contrat sans l'appliquer Document mort, personne ne le lit Le relier à contract.enforced + freshness dbt
Contrat trop verbeux dès le départ Personne n'ose le maintenir Commencer par schéma + qualité + SLA, ajouter le reste après
Ignorer les types Snowflake réels Casts implicites coûteux, échecs de build Vérifier le data_type réel dans le contrat dbt
Pas de versionnement du contrat Impossible de savoir qui consomme quoi, ni quand ça a changé SemVer + le contrat dans Git, revu en merge request
Contrat propriétaire maison Format non standard, non validable, non parseable Adopter ODCS (standard ouvert Apache 2.0)
Pas de FUTURE GRANT Nouvelles tables invisibles aux consommateurs GRANT ... ON FUTURE TABLES systématique
Changer un type sans prévenir Rapports qui cassent en production Version majeure + notification des consommateurs
Le contrat décrit mais ne mesure pas Promesse non vérifiée freshness + règles quality appliquées

Pourquoi commencer maintenant

Un data contract ne résout pas tout du jour au lendemain, mais il déplace le problème au bon endroit. Avant, un changement de schéma cassait un rapport en production et l'équipe marketing découvrait l'erreur des jours plus tard. Avec un contrat appliqué par dbt et vérifié en CI, la même erreur bloque le merge — le coût est immédiat, localisé, et connu de celui qui l'a causée.

La bonne nouvelle : tu n'as pas besoin de réécrire ton architecture. Le contrat se pose sur l'existant — sur les modèles dbt que tu as déjà (contract.enforced), sur les sources que tu as déjà (freshness), sur les grants que tu poses déjà (FUTURE GRANTS). C'est une couche de formalisation, pas une refonte.

Commence petit : prends une table critique consommée par plusieurs équipes, écris son contrat ODCS, applique-le dans dbt, et observe ce qui se passe au prochain changement de schéma. Je parie que tu ne reviendras pas en arrière — et que l'équipe marketing te remerciera.

M

Mikael Paulhiout

Lead Tech / Data Architect. Écrit sur Snowflake, DBT, Airflow et l'IA appliquée.

RSS LinkedIn