Dataform vous permet d’écrire du JavaScript réel n’importe où dans votre projet. dbt utilise le templating Jinja2. Cette différence semble superficielle — les deux génèrent du SQL au moment de la compilation — mais le fossé philosophique affecte chaque aspect de la façon dont vous construisez et maintenez un projet de transformation.
JavaScript est un langage de programmation complet. Vous disposez de boucles, de conditionnels, de fonctions, de modules, de closures et de tout l’écosystème Node.js. Jinja2 est un langage de templating. Vous disposez de la substitution de variables, du flux de contrôle, des macros et des filtres. La frontière est importante lorsque votre projet devient complexe.
Où la syntaxe diffère
Pour les opérations de base, la traduction est mécanique.
Conditionnel Dataform :
${when(incremental(), `AND updated_at > (SELECT MAX(updated_at) FROM ${self()})`)}Conditionnel dbt :
{% if is_incremental() %} AND updated_at > (SELECT MAX(updated_at) FROM {{ this }}){% endif %}Variable Dataform :
const threshold = 30;Variable dbt :
{% set threshold = 30 %}Appel de fonction Dataform :
${helpers.unnest_event_param('page_location')}Appel de macro dbt :
{{ unnest_event_param('page_location') }}Ces conversions sont fastidieuses mais prévisibles. Un script de recherche-remplacement gère 80 % d’entre elles. Les 20 % restants sont là où les choses deviennent intéressantes.
Conversion de macros : le terrain intermédiaire
Les includes JavaScript de Dataform correspondent aux macros dbt. La logique reste la même ; la syntaxe change significativement.
Include Dataform :
function unnest_event_param(param_name, value_type = 'string_value') { return `(SELECT value.${value_type} FROM UNNEST(event_params) WHERE key = '${param_name}')`;}
module.exports = { unnest_event_param };Macro dbt :
-- macros/unnest_event_param.sql{% macro unnest_event_param(param_name, value_type='string_value') %}(SELECT value.{{ value_type }} FROM UNNEST(event_params) WHERE key = '{{ param_name }}'){% endmacro %}Ce niveau de conversion est gérable. Les fonctions qui retournent des chaînes SQL deviennent des macros qui génèrent des chaînes SQL. La syntaxe des paramètres change, l’interpolation du template change, mais la structure est reconnaissable.
Là où ça devient plus difficile : les fonctions JavaScript qui utilisent des méthodes de tableau, la manipulation d’objets ou une logique conditionnelle au-delà d’un simple if/else. Jinja a des boucles for, des blocs if, set et namespace — mais pas de .map(), pas de .filter(), pas de .reduce(). Vous réécrivez des algorithmes, pas seulement de la syntaxe.
Génération dynamique de modèles : le vrai fossé
C’est là que la différence philosophique devient un problème pratique. Le JavaScript de Dataform excelle dans la génération dynamique de modèles :
const countries = ["US", "GB", "FR", "DE"];countries.forEach(country => { publish(`reporting_${country}`) .dependencies(["source_table"]) .query(ctx => `SELECT * FROM ${ctx.ref("source_table")} WHERE country = '${country}'`);});Quatre lignes de JavaScript produisent quatre modèles séparés, chacun avec des dépendances DAG correctes. Ajouter un pays au tableau, obtenir un nouveau modèle. Ce pattern passe à l’échelle de dizaines ou centaines de modèles générés.
dbt n’a pas d’équivalent. Jinja s’exécute dans un seul fichier .sql pour produire une seule instruction SQL. Il ne peut pas créer de nouveaux fichiers ni de nouveaux modèles au moment de la compilation. Vos options :
Écrire des modèles individuels. Si vous avez peu de variations (moins de 10), créer simplement des fichiers .sql séparés. C’est verbeux mais déboguable. Chaque modèle est visible dans le DAG, dispose de sa propre configuration de tests et peut être exécuté indépendamment. Pour la plupart des équipes, c’est la bonne réponse.
Utiliser dbt_codegen. Générer les fichiers YAML et SQL une fois avec un script, puis les maintenir manuellement. Cela comble l’écart pour la création initiale mais ne donne pas le dynamisme continu de l’approche Dataform.
Prétraitement externe. Utiliser un script Python pour générer des fichiers .sql avant dbt run. C’est l’équivalent le plus proche de l’approche Dataform mais se situe en dehors de la connaissance de dbt. Votre pipeline CI exécute l’étape de génération, puis dbt récupère les fichiers. Cela fonctionne, mais vous avez ajouté une étape de build que dbt ne connaît pas.
codegen + run-operation de dbt. Pour certains patterns, vous pouvez utiliser dbt run-operation generate_model_yaml pour générer des fichiers. Limité à ce que le package codegen supporte.
Si un projet Dataform repose fortement sur la génération dynamique de modèles, le coût de migration est élevé. C’est le facteur unique le plus important dans la décision de migration.
Différences de comportement subtiles
Au-delà de la syntaxe, JavaScript et Jinja gèrent les cas limites différemment. Ces différences sont invisibles dans les modèles simples mais surgissent lors de la migration d’une logique complexe.
Gestion des null. Les null, undefined et chaînes vides de JavaScript se comportent différemment dans les comparaisons que le none et la chaîne vide de Jinja. Un conditionnel JavaScript if (value) qui était truthy pour 0 et "" ne se traduira pas directement aux règles de vérité de Jinja.
Précision numérique. JavaScript utilise des nombres flottants 64 bits pour tous les nombres. Jinja utilise le système de types Python (les entiers restent des entiers, les flottants sont en 64 bits). Si vos includes JavaScript effectuent des calculs arithmétiques qui alimentent la génération SQL, vérifier que les valeurs générées correspondent après conversion.
Interpolation de chaînes. Les template literals JavaScript `value: ${expr}` deviennent "value: " ~ expr de Jinja ou "value: {{ expr }}" selon le contexte. L’opérateur tilde (~) est l’opérateur de concaténation de Jinja — peu familier pour la plupart des développeurs JavaScript.
Coercition de type. JavaScript coerce silencieusement les types dans les comparaisons et la concaténation. Jinja est plus strict. {{ 5 + "3" }} génère une erreur dans Jinja ; 5 + "3" produit "53" en JavaScript. Si vos includes JavaScript s’appuient sur une coercition implicite, vous trouverez des bugs lors de la conversion.
Ces différences ont causé la casse du pipeline ML d’une équipe après migration. Le SQL généré semblait identique lors d’une inspection superficielle, mais l’arithmétique en virgule flottante dans un include JavaScript produisait des valeurs de seuil légèrement différentes de l’équivalent Jinja. Le modèle s’est réentraîné sur les nouveaux seuils, et la précision de la détection de fraude a chuté jusqu’à ce que quelqu’un remonte le problème jusqu’à une différence de 0,0001 dans une clause WHERE générée.
Quand chaque approche l’emporte
JavaScript (Dataform) excelle quand :
- Vous avez besoin de génération dynamique de modèles à grande échelle
- Votre équipe a de solides compétences JavaScript
- La manipulation complexe des données se produit au niveau du templating
- Vous voulez un contrôle programmatique sur le DAG lui-même
Jinja (dbt) excelle quand :
- SQL est le langage principal, et le templating est secondaire
- Vous voulez un large écosystème de packages et un support communautaire
- Le support multi-warehouse est important
- Vous préférez des définitions de modèles explicites et visibles plutôt que générées
Ni l’une ni l’autre approche n’est universellement meilleure. JavaScript vous donne plus de puissance au prix de plus de complexité. Jinja vous contraint à des patterns plus simples, ce qui pour la plupart des travaux d’analytics engineering est une fonctionnalité, pas une limitation. Le projet dbt typique n’a pas besoin de génération dynamique de modèles — il a besoin de SQL propre avec quelque substitution de variables et réutilisation de code. Jinja gère bien cela.
La question n’est pas quel langage de templating est meilleur. C’est dans quelle mesure la complexité de votre projet réside dans la couche de templating par rapport au SQL lui-même. Si la réponse est « beaucoup », réfléchir soigneusement avant de migrer depuis JavaScript.