ServicesÀ proposNotesContact Me contacter →
EN FR
Note

Génération dynamique de modèles dans Dataform

Comment le JavaScript de Dataform permet la construction programmatique de DAG — générant des dizaines de modèles à partir d'une seule boucle — et ce que les équipes dbt font à la place.

Planté
dataformdbtbigquerydata engineeringdata modelingautomation

La génération dynamique de modèles — écrire une boucle JavaScript une seule fois pour produire des dizaines de modèles entièrement fonctionnels avec suivi de dépendances — est une différence structurelle entre Dataform et dbt, distincte des différences syntaxiques ou de placement des blocs de configuration.

Le pattern

Les fichiers .js de Dataform dans le répertoire definitions/ s’exécutent en JavaScript lors de la compilation. Ils ont accès à la fonction publish(), qui crée un modèle dans le DAG. Tout ce qu’on peut exprimer comme une boucle en JavaScript, on peut l’exprimer comme un ensemble de modèles dans Dataform.

L’exemple canonique : les tables de reporting par pays.

definitions/reporting/country_tables.js
const countries = ["US", "GB", "FR", "DE", "JP", "AU"];
countries.forEach(country => {
publish(`reporting_${country}`)
.dependencies(["base__ga4__events"])
.query(ctx => `
SELECT
event_id,
event_name,
event_timestamp,
user_pseudo_id
FROM ${ctx.ref("base__ga4__events")}
WHERE country_code = '${country}'
`);
});

Six modèles. Un fichier. Ajoutez "CA" au tableau et recompilez — sept modèles. Retirez-en un — cinq modèles. Le moteur DAG de Dataform traite chaque modèle publié comme un nœud de première classe avec suivi de dépendances, planification et support des assertions appropriés.

Le même pattern fonctionne pour toute variation systématique :

definitions/staging/source_tables.js
const sources = [
{ name: "stripe", schema: "stripe_prod" },
{ name: "shopify", schema: "shopify_production" },
{ name: "salesforce", schema: "salesforce_data" }
];
sources.forEach(({ name, schema }) => {
publish(`stg__${name}__orders`)
.type("view")
.query(ctx => `
SELECT *
FROM ${ctx.ref(schema, "orders")}
WHERE _fivetran_deleted IS FALSE
`);
});

Trois vues staging. Modifiez le tableau sources, modifiez les modèles. La configuration du projet pilote l’ensemble des modèles plutôt que le système de fichiers.

Génération plus complexe

Le pattern s’adapte à des cas plus complexes. Modèles incrémentaux avec assertions, générés à partir d’un objet de configuration :

definitions/reporting/tenant_models.js
const tenants = [
{ id: "tenant_a", project: "project-a", dataset: "analytics_123" },
{ id: "tenant_b", project: "project-b", dataset: "analytics_456" }
];
tenants.forEach(tenant => {
publish(`events_${tenant.id}`, {
type: "incremental",
uniqueKey: ["event_id"],
bigquery: {
partitionBy: "DATE(event_timestamp)",
clusterBy: ["user_pseudo_id"]
},
assertions: {
uniqueKey: ["event_id"],
nonNull: ["event_id", "event_timestamp"]
}
})
.query(ctx => `
SELECT
event_id,
event_name,
event_timestamp,
user_pseudo_id
FROM ${ctx.ref(tenant.project, tenant.dataset, "events_*")}
${ctx.when(ctx.incremental(), `WHERE event_timestamp > (SELECT MAX(event_timestamp) FROM ${ctx.self()})`)}
`);
});

Deux modèles, chacun incrémental avec partitionnement, clustering et assertions d’unicité — tous générés à partir d’un tableau de configuration. Ajoutez un tenant, obtenez un modèle.

Ce pattern est pratique pour :

  • L’analytics SaaS multi-tenant où chaque client a un schéma séparé
  • Les pipelines de données régionaux où la même transformation s’exécute par zone géographique
  • Les pipelines multi-sources où le même pattern de staging s’applique à de nombreuses tables sources
  • Les frameworks A/B testing où des modèles séparés suivent les populations contrôle et traitement

Ce que font les équipes dbt à la place

dbt n’a pas de mécanisme équivalent. Jinja s’exécute dans un seul fichier .sql et produit un seul modèle. Il ne peut pas créer de fichiers, ne peut pas ajouter de nœuds au DAG programmatiquement. C’est une contrainte ferme.

Les contournements pratiques, du plus au moins recommandé :

Écrire des fichiers individuels

Pour moins de 10 variations, écrivez-les simplement. Dix fichiers avec du SQL quasi-identique est verbeux, mais chaque modèle est visible dans le DAG, a sa propre entrée dans schema.yml, et peut être sélectionné et exécuté indépendamment. La duplication est réelle ; la debuggabilité aussi.

Le point de seuil où écrire des fichiers individuels devient déraisonnable varie selon l’équipe. Certains tracent la limite à 5, d’autres à 20. Cela dépend de la fréquence de changement de l’ensemble et de la similarité des modèles.

dbt_codegen pour un scaffolding ponctuel

dbt_codegen peut générer du YAML et du SQL de modèle à partir de tables d’entrepôt existantes. Cela gère la création initiale d’un grand ensemble de modèles similaires, mais c’est un scaffold ponctuel, pas une génération continue. Si vous ajoutez un nouveau pays, vous relancez codegen, vérifiez la sortie et committez le nouveau fichier. Le processus est manuel et sujet aux erreurs à grande échelle.

C’est la bonne réponse quand l’ensemble des modèles est stable. C’est la mauvaise réponse quand la configuration change fréquemment.

Pré-traitement externe

L’approche la plus proche de celle de Dataform : un script Python ou shell qui génère des fichiers .sql avant dbt run. Votre pipeline CI exécute l’étape de génération, dbt récupère les fichiers générés, et le DAG inclut les modèles générés.

L’inconvénient est que dbt n’a aucune connaissance de l’étape de génération. Si le générateur et les modèles se désynchronisent, dbt s’exécute avec succès sur des fichiers générés obsolètes. Vous avez ajouté une étape de build qui vit en dehors du graphe de dbt et nécessite une documentation, des tests et une maintenance séparés.

Certaines équipes utilisent ce pattern avec succès. Cela nécessite de la discipline pour traiter le générateur comme faisant partie de la codebase, et non comme un script ponctuel.

dbt run-operation avec Codegen

Pour certains patterns, dbt run-operation generate_model_yaml scaffolde du YAML à partir de tables existantes. La portée est limitée à ce que le package codegen supporte, et le résultat nécessite toujours une gestion manuelle des fichiers.

La génération dynamique de modèles est un avantage capacitaire pour Dataform sur des cas d’usage spécifiques : plateformes multi-tenant, pipelines régionaux à grande échelle, projets où l’ensemble des modèles est piloté par la configuration plutôt qu’un schéma stable.

La plupart des projets d’analytics engineering n’en ont pas besoin. La modélisation dimensionnelle standard a un ensemble de modèles stable qui change rarement. La décision entre Dataform et dbt dépend rarement de cette fonctionnalité seule — l’engagement plateforme, les exigences de tests, la maturité de l’écosystème et les compétences de l’équipe sont le plus souvent les facteurs déterminants.

La note JavaScript vs Jinja en analytics engineering couvre les défis de migration quand des projets se sont appuyés sur la génération dynamique et doivent passer à dbt. Le coût de migration est élevé et constitue l’une des principales raisons pour lesquelles les équipes devraient évaluer cette capacité avant de s’engager sur Dataform.