ServicesÀ proposNotesContact Me contacter →
EN FR
Note

Nommage centré sur l'entité pour les modèles intermédiaires dbt

Pourquoi les modèles intermédiaires doivent être nommés d'après l'entité qu'ils représentent, pas la transformation qu'ils effectuent — et la notation de jointure auto-documentée qui le rend possible.

Planté
dbtdata modelingdata engineering

La documentation officielle de dbt suggère de nommer les modèles intermédiaires d’après la transformation qu’ils effectuent :

  • int_payments_pivoted_to_order
  • int_events_sessionized
  • int_customers_aggregated_by_account

Cette approche s’effondre à grande échelle, pour des raisons qui clarifient également des principes plus larges de conception de la couche intermédiaire.

Noms centrés sur la transformation

Les noms centrés sur la transformation décrivent ce que le SQL fait — « comment ceci a-t-il été construit ? » plutôt que « qu’est-ce que ceci représente ? ». Avec un petit nombre de modèles intermédiaires, cela est gérable, mais avec 30+ modèles, trouver celui qui contient les données client jointes aux commandes nécessite d’ouvrir les fichiers :

  • Est-ce int_customers_enriched_with_orders ?
  • int_orders_with_customer_data ?
  • int_customers_aggregated_by_order_channel ?

Les noms transmettent l’opération SQL mais pas le grain, les entités présentes, ou la relation de jointure.

Les noms centrés sur la transformation ont également tendance à encourager la construction de modèles autour de la commodité plutôt que des concepts, conduisant à des modèles comme int_orders_for_the_finance_mart et int_orders_for_attribution — une logique similaire dupliquée pour différents consommateurs, divergeant dans le temps.

Nommage centré sur l’entité

L’alternative : nommer les modèles intermédiaires d’après l’entité qu’ils représentent, au grain qu’ils maintiennent.

Il existe deux types de modèles intermédiaires, et chacun a son propre pattern de nommage :

Modèles d’entité purs — Lorsque vous appliquez une logique métier à un seul modèle base (sessionisation, déduplication, calculs complexes qui ne nécessitent pas une autre entité) :

int__[entité]int__session, int__customer, int__conversion

Modèles d’entité enrichis — Lorsque vous joignez des entités ensemble :

int__[entité_principale]__[entité1]_[type_jointure]_[entité2]

Les abréviations de type de jointure sont :

  • lj = LEFT JOIN
  • ij = INNER JOIN
  • cj = CROSS JOIN

Ainsi : int__customer__customer_lj_order, int__session__session_lj_conversion, int__customer__customer_lj_order_lj_session.

Pourquoi le pattern de nommage enrichi est verbeux par conception

Le pattern int__customer__customer_lj_order est intentionnellement verbeux.

En lisant le nom du modèle, on sait immédiatement :

  1. Le grain : customer (l’entité principale)
  2. Quelles données sont présentes : enregistrements client enrichis avec des données de commande
  3. La relation de jointure : left join (les commandes peuvent ne pas exister pour tous les clients)

Inutile d’ouvrir le fichier SQL. Inutile d’interroger information_schema.columns. Le nom est auto-documenté exactement dans les dimensions qui comptent pour le travail en aval.

Pour plusieurs jointures, les chaîner :

int__customer__customer_lj_order_lj_session

Cela se lit comme : modèle au grain client, left-joiné aux commandes, left-joiné aux sessions. On sait que le grain n’a pas changé depuis le premier mot. On sait qu’aucun client n’est manquant (left joins). On sait quelles données sont disponibles.

Comparez à l’alternative centrée sur la transformation : int_customers_with_orders_and_sessions_aggregated. Quel est le grain ? Est-ce au grain client, ou les sessions ont-elles été agrégées au niveau client ? Tous les clients sont-ils présents ? Il faut le SQL pour savoir.

Quand ne pas créer de modèle intermédiaire

Ne pas créer int__customer s’il est identique à base__crm__customer. La couche intermédiaire doit apporter de la valeur grâce aux jointures, à la logique métier, ou aux transformations. Un modèle intermédiaire qui passe à travers le modèle base sans changements ajoute un nœud DAG et un niveau d’indirection sans bénéfice.

Créer int__customer lorsque vous :

  • Fusionnez des enregistrements clients depuis les systèmes CRM et de facturation (résolution d’identité)
  • Ajoutez des champs dérivés (segment client, paliers de valeur vie client, classification de segment)
  • Déduplication entre les sources
  • Appliquez une logique métier qui transforme la façon dont vous pensez à l’entité

Ne pas le créer simplement parce que « les modèles base ne doivent avoir qu’une seule source » ou comme wrapper de passage. L’architecture dbt en trois couches n’est pas une question d’avoir exactement trois sauts pour chaque modèle ; c’est une question d’avoir une séparation appropriée entre le nettoyage brut, la construction d’entités, et la consommation.

Organiser les modèles intermédiaires par entité

Avec le nommage centré sur l’entité, l’organisation des dossiers suit naturellement :

models/
└── intermediate/
├── _int__models.yml
├── session/
│ ├── int__session.sql
│ └── int__session__session_lj_conversion.sql
├── customer/
│ ├── int__customer.sql
│ └── int__customer__customer_lj_order.sql
├── int__unified_ad_spend.sql # Fichier plat pour les modèles uniques
└── int__attribution_touchpoint.sql

Les sous-dossiers par entité fonctionnent lorsque vous avez 3+ modèles pour la même entité. Rester plat pour les projets plus petits. La contrainte clé : ne jamais organiser les intermédiaires par domaine métier (intermediate/marketing/, intermediate/finance/). Un modèle int__customer__customer_lj_order sert à la fois le marketing et la finance — le forcer dans le dossier d’un domaine crée des frontières artificielles et rend difficile sa recherche.

Les modèles intermédiaires servent les besoins de réutilisation interne du projet, pas les patterns de consommation des équipes spécifiques. Les équipes trouvent leurs modèles dans la couche mart.

Un exemple d’analytics marketing

Pour une configuration GA4 et de plateformes publicitaires, la couche intermédiaire ressemble à ceci :

intermediate/
├── session/
│ ├── int__session.sql # Sessionisation des événements GA4
│ └── int__session__session_lj_conversion.sql # Sessions enrichies avec les conversions
├── int__conversion.sql # Événements de conversion (modèle d'entité)
├── int__unified_ad_spend.sql # Dépenses publicitaires UNIONées entre plateformes
└── int__attribution_touchpoint.sql # Tous les points de contact pour l'attribution

En lisant ces noms, on sait :

  • int__session maintient le grain de session avec la logique métier appliquée
  • int__session__session_lj_conversion maintient le grain de session, enrichi avec les données de conversion via left join (les sessions sans conversions sont toujours présentes)
  • int__unified_ad_spend est un cas spécial — « unified » signale un UNION entre plateformes, qui est le concept d’entité

L’alternative — int_events_sessionized_with_30_minute_timeout, int_sessions_with_conversion_data_left_joined — échange la concision contre du bruit sans gagner en clarté significative.

Le nommage comme discipline de conception

Le nommage centré sur l’entité exige de répondre à « quelle entité ceci représente-t-il ? » avant de créer un modèle. Si la réponse est peu claire, le modèle est probablement une requête extraite d’un mart par commodité plutôt qu’une entité cohérente. Les entités de la couche intermédiaire doivent correspondre à des concepts métier — sessions, clients, commandes, campagnes, conversions — pas aux besoins en données d’un rapport spécifique en aval. Cette contrainte est ce qui rend la couche intermédiaire réutilisable.