RetourStructurer un projet dbt : les conventions qui comptent
Pourquoi un projet dbt déraille
Vous connaissez l’histoire. Le projet dbt démarre bien : 10 modèles, une structure simple, tout le monde s’y retrouve. Six mois plus tard, vous avez 200 modèles, des CTE de 500 lignes, des noms comme final_v2_clean_backup, zéro test, et une documentation qui vit dans un Google Doc que personne n’a mis à jour depuis le sprint 3
Ce n’est pas un problème spécifique à dbt. C’est le problème universel du code non structuré. La différence, c’est que dbt est tellement facile à démarrer qu’il encourage la dette technique. Un analyste qui connaît SQL peut créer un modèle dbt en 5 minutes. C’est sa force et son piège
Dans le monde logiciel, personne ne coderait une application sans architecture, sans naming conventions, sans tests. Pourtant, c’est exactement ce que font la majorité des équipes data avec leurs projets dbt. Le résultat est prévisible : un projet qui ralentit, des erreurs silencieuses, et des nouveaux arrivants qui mettent deux semaines à comprendre où trouver quoi
La bonne nouvelle : les conventions qui font la différence ne sont ni compliquées ni coûteuses à mettre en place. Elles demandent juste d’être décidées avant le premier modèle, pas après le deux-centième
La première convention à poser est aussi la plus structurante : comment organiser vos modèles en couches distinctes
Le modèle en 3 layers
Le standard de l’industrie pour structurer un projet dbt est le modèle en 3 layers (couches). Chaque couche a une responsabilité unique et clairement définie
Staging : nettoyer, pas transformer
Le staging est le point d’entrée. Il y a un modèle staging par table source, et sa responsabilité se limite au nettoyage minimal :
- Renommer les colonnes pour respecter les conventions (snake_case, noms explicites)
- Caster les types (une date stockée en string → date)
- Dédupliquer si nécessaire (row_number sur la clé primaire)
- Filtrer les enregistrements manifestement invalides (nulls sur la clé primaire)
Un modèle staging ne fait jamais de jointures, d’agrégations, ni de logique métier. Si vous vous retrouvez à écrire un JOIN dans un modèle staging, il va dans intermediate
-- models/staging/erp/stg_erp__commandes.sql
with source as (
select * from {{ source('erp', 'orders') }}
),
renamed as (
select
cast(order_id as int) as commande_id,
cast(customer_id as int) as client_id,
cast(order_date as date) as date_commande,
cast(total_amount as numeric(12, 2)) as montant_ttc,
lower(trim(status)) as statut,
cast(_loaded_at as timestamp) as charge_le
from source
)
select * from renamed
Intermediate : la logique métier
Les modèles intermediate portent la logique métier : jointures entre sources, calculs dérivés, enrichissements. C’est ici que vous combinez les commandes avec les clients, calculez les marges, et appliquez les règles de gestion
-- models/intermediate/int_commandes_enrichies.sql
with commandes as (
select * from {{ ref('stg_erp__commandes') }}
),
clients as (
select * from {{ ref('stg_crm__clients') }}
),
joined as (
select
c.commande_id,
c.date_commande,
c.montant_ttc,
c.statut,
cl.nom_client,
cl.segment,
cl.region
from commandes c
left join clients cl on c.client_id = cl.client_id
)
select * from joined
Les modèles intermediate ne sont pas exposés aux consommateurs finaux (BI, API). Ce sont des briques internes. Leur naming commence par int_ pour signaler qu’ils ne doivent pas être requêtés directement par les analystes métier
Marts : les modèles exposés
Les marts sont les modèles finaux, ceux que les utilisateurs de BI et les data scientists consomment. Ils sont optimisés pour la lisibilité et la performance, pas pour la réutilisabilité du code
On distingue deux types de marts :
fct_(faits) : des événements horodatés (commandes, paiements, connexions, incidents). Un fait a une date et un grain.dim_(dimensions) : des entités référentielles (clients, produits, régions). Une dimension enrichit un fait
-- models/marts/fct_commandes_mensuelles.sql
with commandes as (
select * from {{ ref('int_commandes_enrichies') }}
)
select
date_trunc('month', date_commande) as mois,
segment,
region,
count(*) as nombre_commandes,
sum(montant_ttc) as chiffre_affaires,
count(distinct commande_id) filter (where statut = 'annulee')
as commandes_annulees,
round(avg(montant_ttc), 2) as panier_moyen
from commandes
where statut != 'brouillon'
group by 1, 2, 3
Faut-il un 4e layer ? Certaines équipes ajoutent un layer base (entre source et staging) ou utils (macros et helpers). Notre recommandation : pas avant 100 modèles. Avec 50 modèles, 3 layers suffisent largement. Ajouter des layers prématurément, c’est de la sur-ingénierie qui ralentit l’onboarding sans apporter de valeur
Trois layers bien définis ne servent à rien si les modèles qui les peuplent portent des noms incompréhensibles. Le naming est le complément direct de l’architecture en couches
Naming conventions
Le naming est le premier investissement rentable dans un projet dbt. Un bon naming rend le projet navigable sans documentation : le nom du modèle suffit à comprendre ce qu’il fait et d’où il vient
Préfixes de modèles
| Préfixe | Layer | Exemple |
|---|---|---|
stg_ |
Staging | stg_erp__commandes |
int_ |
Intermediate | int_commandes_enrichies |
fct_ |
Marts (fait) | fct_commandes_mensuelles |
dim_ |
Marts (dim) | dim_clients |
Sources : double underscore
Les modèles staging utilisent le pattern stg_{source}__{table}. Le double underscore (__) sépare le système source du nom de la table :
stg_erp__commandes→ tablecommandesdu système ERPstg_crm__clients→ tableclientsdu CRMstg_stripe__paiements→ tablepaiementsde Stripe
Ce pattern est précieux quand vous avez des tables de même nom dans des sources différentes (stg_erp__produits vs stg_catalogue__produits)
À noter : le double underscore est une convention dbt Labs largement adoptée, pas une règle technique. Un simple underscore (stg_erp_commandes) reste viable si l’équipe préfère, l’important est la cohérence sur l’ensemble du projet
Colonnes
- snake_case systématique :
date_commande, pasDateCommandenidate-commande - Pas de préfixe table dans les colonnes :
client_id, pascommande_client_id(sauf en cas d’ambiguïté) - Dates : suffixe
_atpour les timestamps (cree_leoucreated_at),_datepour les dates calendaires (date_commande) - Booléens : préfixe
est_ouis_(est_actif,is_deleted)
Arborescence du projet
models/
├── staging/
│ ├── erp/
│ │ ├── _erp__sources.yml
│ │ ├── _erp__models.yml
│ │ ├── stg_erp__commandes.sql
│ │ ├── stg_erp__produits.sql
│ │ └── stg_erp__clients.sql
│ └── stripe/
│ ├── _stripe__sources.yml
│ ├── _stripe__models.yml
│ └── stg_stripe__paiements.sql
├── intermediate/
│ ├── _int__models.yml
│ ├── int_commandes_enrichies.sql
│ └── int_paiements_reconcilies.sql
└── marts/
├── _marts__models.yml
├── fct_commandes_mensuelles.sql
├── fct_paiements.sql
├── dim_clients.sql
└── dim_produits.sql
Le fichier _*__models.yml par dossier contient les tests et la documentation des modèles de ce dossier. Le préfixe _ le fait apparaître en premier dans l’arborescence. C’est une convention dbt Labs que nous recommandons systématiquement
Un projet bien nommé et bien organisé reste fragile sans vérification automatisée. Les layers et le naming garantissent la lisibilité, les tests garantissent la fiabilité
Tests : le filet de sécurité
dbt rend le testing de données aussi simple que le testing logiciel. Si vous n’écrivez pas de tests dbt, vous avez choisi de découvrir vos erreurs de données en réunion de direction : le scénario cauchemar de tout data analyst
Les 4 tests natifs
dbt fournit quatre tests génériques intégrés, déclarés en YAML :
# models/marts/_marts__models.yml
models:
- name: fct_commandes_mensuelles
description: 'Commandes agrégées par mois, segment et région'
columns:
- name: mois
description: 'Premier jour du mois'
tests:
- not_null
- name: nombre_commandes
tests:
- not_null
- name: chiffre_affaires
tests:
- not_null
- name: panier_moyen
tests:
- not_null
- name: dim_clients
description: 'Référentiel clients enrichi'
columns:
- name: client_id
description: 'Identifiant unique du client'
tests:
- unique
- not_null
- name: segment
tests:
- accepted_values:
values: ['enterprise', 'mid-market', 'startup', 'particulier']
- name: region
tests:
- not_null
Les quatre tests natifs couvrent 80 % des besoins :
not_null: la colonne ne contient pas de valeurs nullesunique: chaque valeur est unique (clés primaires)accepted_values: les valeurs sont dans une liste prédéfinie (statuts, segments)relationships: intégrité référentielle (la FK existe dans la table référencée)
Tests custom
Pour les règles métier spécifiques, dbt permet d’écrire des tests en SQL pur. Un test custom retourne les lignes qui échouent :
-- tests/assert_montant_ttc_positif.sql
select
commande_id,
montant_ttc
from {{ ref('fct_commandes_mensuelles') }}
where chiffre_affaires < 0
Si cette requête retourne des lignes, le test échoue. Sinon, il passe. Simple, puissant, et infiniment extensible
Stratégie de couverture
Ne testez pas tout avec la même rigueur. Priorisez par impact :
| Layer | Couverture cible | Tests prioritaires |
|---|---|---|
| Marts | 100 % (cible) | unique, not_null, accepted_values, métier |
| Intermediate | 80 % | not_null sur les clés, logique métier |
| Staging | Basique | not_null sur la clé primaire, unique |
Note : la cible 100 % sur marts s’applique aux marts critiques (KPI exposés, reporting réglementaire). En pratique, on observe 90-95 % selon la maturité des données sources et l’ancienneté du projet.
Pour aller plus loin sur les tests avancés (dbt-expectations, elementary, monitoring en production), consultez notre article dédié Tester ses données comme on teste son code
Les tests détectent les anomalies, mais ils ne disent pas ce qu’un modèle est censé représenter ni pourquoi il existe. Pour ça, il faut documenter, et dbt propose un mécanisme qui élimine l’excuse classique du “pas le temps”
Documentation-as-code
La documentation dbt vit dans les fichiers YAML, à côté du code SQL. C’est le principe du docs-as-code : la documentation est versionnée, revue en code review, et synchronisée avec le code dès lors que la code review impose la mise à jour des descriptions à chaque PR. Sans cette discipline, la doc dérive comme partout ailleurs
# models/marts/_marts__models.yml (extrait)
models:
- name: dim_clients
description: >
Référentiel clients enrichi. Combine les données CRM (identité, segment)
avec l'historique de commandes (première commande, dernière commande,
nombre total de commandes). Mis à jour quotidiennement.
columns:
- name: client_id
description: 'Identifiant unique du client, issu du CRM'
- name: segment
description: >
Segment commercial du client. Valeurs possibles :
enterprise, mid-market, startup, particulier.
Déterminé par le chiffre d'affaires annuel.
- name: date_premiere_commande
description: 'Date de la première commande validée du client'
- name: nombre_commandes_total
description: 'Nombre total de commandes validées (hors brouillons et annulées)'
La commande dbt docs generate produit un site statique navigable avec :
- La liste de tous les modèles avec leurs descriptions
- Le détail de chaque colonne
- Un graphe de lineage interactif montrant les dépendances entre modèles
- Les résultats des derniers tests
Ce site est déployable en interne (un simple serveur web statique) et remplace avantageusement le Confluence que personne ne met à jour
La règle d’or : la documentation est revue en code review, comme le code. Une pull request qui ajoute un modèle sans description est refusée. Ce n’est pas du perfectionnisme, c’est la seule façon de maintenir une documentation à jour dans la durée
Layers, naming, tests, documentation : ces quatre piliers se renforcent mutuellement. Voici ce que donne leur application conjointe sur un projet réel
Retour terrain : restructurer un projet dbt existant
Une ETI du REX composite Nobori (cas représentatif anonymisé issu de plusieurs missions 2023-2026) a démarré son projet dbt sans conventions. Après 3 mois et 80 modèles à plat, le constat était sans appel :
Avant la restructuration :
- 80 modèles dans un dossier unique (pas de layers)
- Naming incohérent :
commandes_final,clients_v2,monthly_revenue_clean - 0 tests
- Documentation sur Confluence, obsolète depuis 6 semaines
- Onboarding d’un nouvel analyste : 2 semaines pour comprendre le projet
Nous avons mené une restructuration sur 2 semaines, en parallèle de la production (pas d’interruption de service ; calendrier tendu mais tenable si l’équipe a déjà une bonne maîtrise SQL et Git) :
- Semaine 1 : réorganisation en 3 layers (staging/intermediate/marts), renaming systématique avec les préfixes standards, ajout des fichiers
_*__models.yml - Semaine 2 : ajout de tests (unique + not_null sur toutes les clés, accepted_values sur les statuts critiques), documentation des 20 marts principaux
Après la restructuration :
- 3 layers propres, 82 modèles restructurés
- Couverture de tests : 95 % sur les marts, 70 % sur intermediate
- Documentation générée par
dbt docs generate, déployée en interne - Onboarding d’un nouvel analyste : 2 jours
Le gain le plus spectaculaire n’est pas technique : c’est la confiance. Les équipes métier qui consommaient les données ont rapidement remarqué que les erreurs étaient détectées avant d’arriver dans les dashboards. La satisfaction interne est passée de 3,2/10 à 8,1/10 en deux mois
Ce retour d’expérience illustre le chemin idéal. En pratique, la structuration comporte des pièges récurrents que nous retrouvons d’un projet à l’autre
Les 4 pièges de la structuration
1. Sur-ingénierer les layers. Trois layers suffisent pour 90 % des projets. Ajouter des layers base, utils, reporting, analytics avant d’avoir 100 modèles, c’est créer de la complexité sans bénéfice. Commencez simple, ajoutez un layer quand la douleur est réelle, pas quand elle est théorique
2. Copier-coller une convention GitHub sans l’adapter. Le dbt style guide de GitLab est excellent, mais il a été conçu pour une organisation de 2 000 personnes avec 500 modèles dbt. Votre ETI de 200 personnes avec 50 modèles n’a pas les mêmes besoins. Prenez les principes (layers, préfixes, tests), adaptez le reste à votre contexte
3. Ignorer la dette existante. “On structurera le projet plus tard, quand on aura le temps.” Ce moment n’arrive jamais. La dette s’accumule de manière exponentielle : chaque modèle ajouté sans convention rend la restructuration plus coûteuse. Si vous avez déjà 50+ modèles sans structure, planifiez un sprint de restructuration maintenant, pas au prochain trimestre
4. Ne pas impliquer les analystes dans les conventions. Des conventions imposées par l’architecte data sans consultation des analystes seront contournées. Impliquez l’équipe dans la définition des règles : quels préfixes, quelles colonnes sont obligatoires, quel niveau de documentation. Les conventions co-construites sont les seules qui tiennent
Conclusion
Structurer un projet dbt n’est ni difficile ni coûteux. C’est une décision, prise au bon moment (dès le départ) ou au mauvais moment (après 200 modèles), mais c’est la même décision
Les conventions présentées ici (3 layers, naming avec préfixes, tests systématiques, documentation-as-code) ne sont pas des opinions. Ce sont les standards de l’industrie, validés par des milliers de projets dbt en production. Vous pouvez les adapter à votre contexte, mais les ignorer, c’est s’engager sur un chemin que chaque équipe data regrette
Nobori accompagne les ETI dans la structuration et l’industrialisation de leurs projets dbt. Découvrir notre offre Data
Sources et méthodologie
- dbt Labs, “Best practices guide” : conventions de structure, naming, testing recommandées par dbt Labs
- GitLab, “dbt style guide” : référence communautaire pour les conventions dbt en entreprise
- Retours d’expérience Nobori : données issues de nos missions de structuration dbt auprès d’ETI (métriques avant/après anonymisées)
Les métriques du retour terrain sont issues de mesures réelles. La recommandation des 3 layers comme standard est basée sur la documentation officielle dbt Labs et nos observations sur une dizaine de missions accompagnées entre 2023 et 2026.
Pour aller plus loin
- Approfondissez les tests avec notre guide Tester ses données comme on teste son code : data quality avec dbt
- Revenez aux fondamentaux avec dbt a tué l’ETL traditionnel si vous débutez avec dbt
- Explorez notre expertise Platform Engineering pour comprendre comment dbt s’intègre dans une plateforme data
Ce sujet vous concerne ?
Évaluez votre maturité en Data & Analytics en 30 min avec un expert senior. Sans engagement

Lead Data
Ce sujet vous intéresse ?
Évaluez votre maturité avec un diagnostic de 30 min.
Newsletter
Restez informé
Analyses Cloud, Data & IA : 1 email par mois, pas plus
Inscription confirmée
Merci ! Vous recevrez notre prochaine analyse directement dans votre boîte mail


