Vous utilisez Dataform sur BigQuery, et ça fonctionne. Le SQL compile, les planifications s’exécutent, et vos parties prenantes reçoivent leurs données à temps. Mais quelque chose a changé. Peut-être que votre entreprise a acquis une équipe sous Snowflake. Peut-être que vous en avez assez d’implémenter manuellement des logiques de test que les packages dbt gèrent nativement. Peut-être avez-vous remarqué que chaque offre d’emploi en analytics engineering liste dbt comme prérequis.
Quelle que soit la raison, vous envisagez une migration. Ce guide couvre le processus de l’évaluation à la validation, y compris les conversions mécaniques et les parties qui demandent un vrai effort.
Quand la migration a vraiment du sens
Tous les projets Dataform ne doivent pas migrer. La décision se résume à trois questions.
Passez-vous au multi-warehouse ? Dataform ne fonctionne qu’avec BigQuery. Si votre organisation adopte Snowflake, Databricks ou Redshift en complément de BigQuery, l’architecture d’adaptateurs de dbt devient essentielle. C’est le signal de migration le plus clair.
Avez-vous besoin de l’écosystème ? Le hub de packages dbt propose plus de 200 packages couvrant tout, de la qualité des données (dbt_expectations, Elementary) à l’attribution marketing (dbt-ga4 de Velir) en passant par les fonctions utilitaires (dbt_utils). Si vous implémentez manuellement des fonctionnalités qui existent en tant que package dbt, la migration se rentabilise par la réduction de la maintenance.
La portabilité de carrière compte-t-elle ? La maîtrise de dbt apparaît dans la plupart des offres d’emploi en analytics engineering. L’expertise Dataform reste précieuse mais concentrée dans les organisations fortement orientées GCP. Si les membres de votre équipe se soucient de leur CV, l’expérience dbt a une applicabilité plus large.
Quand rester en place
La migration a un coût réel. Restez sur Dataform si :
- Vos includes JavaScript sont complexes. La capacité de Dataform à générer des modèles programmatiquement avec du JavaScript standard n’a pas d’équivalent direct dans dbt. Convertir des fichiers
.jssophistiqués qui créent dynamiquement des dizaines de modèles nécessite une réécriture substantielle. - Des pipelines ML dépendent du comportement du templating. La migration d’une équipe a pris deux mois, plus trois semaines supplémentaires pour corriger des problèmes où le templating JavaScript différait de Jinja de manières qui cassaient le réentraînement des modèles. La fraude qui s’est glissée pendant cette période a coûté plus que des années de frais de licence.
- Votre cas d’usage est simple. Si vous exécutez des modèles de transformation basiques sans logique incrémentale complexe, le tier gratuit de Dataform sur BigQuery fait sens économiquement.
Le calcul de rentabilité : si la migration prend trois mois de temps d’ingénierie et que vous comparez avec dbt Cloud à 100 $/utilisateur/mois, il faut une équipe de 10 personnes pendant plus de deux ans avant que les économies de licence seules justifient le changement. Les besoins multi-warehouse ou les exigences d’écosystème changent cette équation immédiatement.
Comprendre la correspondance conceptuelle
Avant de toucher au code, comprenez comment les concepts Dataform se transposent dans dbt. Certaines correspondances sont directes ; d’autres nécessitent de repenser votre approche.
Équivalents directs
| Dataform | dbt | Notes |
|---|---|---|
${ref("model")} | {{ ref('model') }} | Même concept, syntaxe différente |
config { type: "table" } | {{ config(materialized='table') }} | Déclaration de matérialisation |
config { type: "view" } | {{ config(materialized='view') }} | Par défaut dans les deux outils |
.sqlx files | .sql files | Changement d’extension |
definitions/ folder | models/ folder | Nommage des répertoires |
Changements conceptuels
Sources vs. déclarations. Dataform utilise des fichiers de déclaration pour définir les tables sources. dbt utilise des définitions de sources en YAML qui remplissent le même rôle mais ajoutent les vérifications de fraîcheur et la documentation au même endroit.
Déclaration Dataform :
declare({ database: "my-project", schema: "analytics_123456789", name: "events_*"});Source dbt :
sources: - name: ga4 database: my-project schema: analytics_123456789 tables: - name: events identifier: "events_*" freshness: warn_after: {count: 24, period: hour}Jinja vs. templating JavaScript. Dataform permet d’écrire du vrai JavaScript (boucles, conditions, fonctions) n’importe où dans votre projet. dbt utilise le templating Jinja2, qui ressemble visuellement mais se comporte différemment. Cet écart philosophique, exploré dans mon comparatif JavaScript vs Jinja, affecte chaque aspect de la migration.
Condition Dataform :
${when(incremental(), `AND updated_at > (SELECT MAX(updated_at) FROM ${self()})`)}Condition dbt :
{% if is_incremental() %} AND updated_at > (SELECT MAX(updated_at) FROM {{ this }}){% endif %}La syntaxe est différente mais gérable. Le vrai défi vient de la génération dynamique de modèles, que nous couvrirons plus loin.
Les lacunes à anticiper
Certaines fonctionnalités Dataform n’ont pas d’équivalent direct dans dbt :
- Seeds. Les seeds dbt sont des fichiers CSV qui se chargent comme des tables. Dataform n’a pas d’équivalent, donc vous avez probablement des tables BigQuery remplissant ce rôle que vous déclarerez comme sources.
- Snapshots. La fonctionnalité snapshot de dbt pour les tables SCD Type 2 nécessite une implémentation manuelle dans Dataform. Si vous avez construit une logique SCD personnalisée, vous la convertirez vers la syntaxe snapshot plus simple de dbt.
- Packages. Dataform n’a pas d’écosystème de packages. Tous les includes personnalisés que vous avez écrits en JavaScript doivent être convertis en macros dbt ou remplacés par des packages existants.
Configurer votre environnement dbt pour BigQuery
La correspondance conceptuelle étant claire, configurez votre projet dbt.
dbt Core vs. dbt Cloud
Pour les équipes BigQuery migrant depuis Dataform, commencez par dbt Core sauf si vous avez besoin immédiatement de fonctionnalités spécifiques à Cloud (voir mon comparatif dbt Core vs Cloud pour les détails). Raisons :
- Zéro coût supplémentaire pendant la migration (vous êtes déjà habitué à la gratuité avec Dataform)
- Le développement local correspond à votre workflow actuel
- Passage à Cloud ultérieur si vous avez besoin du semantic layer, de Mesh ou de la gouvernance entreprise
Installez dbt avec l’adaptateur BigQuery :
pip install dbt-bigqueryInitialisation du projet
Créez un nouveau projet dbt :
dbt init my_projectConfigurez votre connexion BigQuery dans ~/.dbt/profiles.yml :
my_project: outputs: dev: type: bigquery method: oauth project: my-gcp-project dataset: dbt_dev threads: 4 location: US target: devComparaison des structures de répertoires
Votre structure de projet Dataform se transpose dans dbt ainsi :
# Dataform # dbtdefinitions/ models/ sources/ base/ staging/ intermediate/ reporting/ marts/includes/ macros/dataform.json dbt_project.ymlLe répertoire models/ remplace definitions/. Organisez par couche (base, intermediate, marts) plutôt que par système source pour respecter les conventions dbt. Pour un aperçu approfondi, consultez mon guide des couches de modèles dbt.
Convertir vos modèles étape par étape
Procédez à la conversion par phases, en validant chacune avant de passer à la suivante.
Phase 1 : Conversions basiques de tables et vues
Commencez par les modèles simples sans logique incrémentale ni templating complexe. Ce sont des conversions mécaniques.
Dataform :
config { type: "table", schema: "reporting"}
SELECT customer_id, SUM(order_total) AS lifetime_valueFROM ${ref("base_orders")}GROUP BY 1dbt :
{{ config( materialized='table', schema='reporting') }}
SELECT customer_id, SUM(order_total) AS customer__lifetime_valueFROM {{ ref('base__shopify__orders') }}GROUP BY 1La conversion est directe : changez la syntaxe du bloc config, remplacez ${ref()} par {{ ref() }}, et enregistrez avec une extension .sql.
Phase 2 : Déclarations de sources
Remplacez les déclarations Dataform par des fichiers YAML de sources dbt. Créez un fichier source par système source dans votre dossier base.
version: 2
sources: - name: ga4_raw database: "{{ var('ga4_project') }}" schema: "{{ var('ga4_dataset') }}" tables: - name: events identifier: "events_*" description: "Raw GA4 event export"Dans vos modèles, remplacez les références aux sources :
-- Dataform: ${ref("analytics_123456789", "events_*")}-- dbt:SELECT event_date, event_timestamp, event_name, user_pseudo_idFROM {{ source('ga4_raw', 'events') }}Définissez les variables dans dbt_project.yml :
vars: ga4_project: my-gcp-project ga4_dataset: analytics_123456789Phase 3 : Modèles incrémentaux
La syntaxe incrémentale de Dataform se traduit vers la macro is_incremental() de dbt (couvert en détail dans mon guide des modèles incrémentaux dbt) :
Dataform :
config { type: "incremental", uniqueKey: ["event_id"], updatePartitionFilter: "event_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 3 DAY)"}
SELECT event_id, event_date, event_name, user_pseudo_idFROM ${ref("base_events")}${when(incremental(), `WHERE event_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 3 DAY)`)}dbt :
{{ config( materialized='incremental', unique_key='event_id', partition_by={ 'field': 'event_date', 'data_type': 'date' }, incremental_strategy='merge') }}
SELECT event_id, event_date, event_name, user_pseudo_idFROM {{ ref('base__ga4__events') }}{% if is_incremental() %}WHERE event_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 3 DAY){% endif %}Différences clés :
- dbt requiert une configuration
partition_byexplicite pour BigQuery - La
incremental_strategyestmergepar défaut pour BigQuery, mais la rendre explicite améliore la lisibilité updatePartitionFilterest intégré à votre logique de clauseWHERE
Phase 4 : Migration des tests
Les assertions inline de Dataform deviennent des tests YAML dans dbt.
Dataform :
config { type: "table", assertions: { uniqueKey: ["customer_id"], nonNull: ["customer_id", "email"], rowConditions: ['email LIKE "%@%.%"'] }}dbt :
version: 2
models: - name: mrt__sales__customers columns: - name: customer_id tests: - unique - not_null - name: customer__email tests: - not_nullPour les conditions de lignes personnalisées, utilisez le package dbt_expectations :
- name: customer__email tests: - dbt_expectations.expect_column_values_to_match_regex: regex: "^.+@.+\\..+$"Installez les packages en les ajoutant dans packages.yml :
packages: - package: calogica/dbt_expectations version: 0.10.4Puis lancez dbt deps pour installer.
Gérer les parties difficiles
Les sections précédentes couvrent les conversions mécaniques. Passons maintenant aux parties qui demandent une vraie réflexion.
Conversion des macros
Les includes JavaScript de Dataform deviennent des macros dbt. La logique reste similaire ; 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 %}L’utilisation passe de ${helpers.unnest_event_param('page_location')} à {{ unnest_event_param('page_location') }}.
Génération dynamique de modèles
Le JavaScript de Dataform excelle dans la génération dynamique de modèles, tandis que dbt n’a pas d’équivalent propre. Si vous avez du code comme celui-ci :
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}'`);});dbt n’a pas d’équivalent direct. Vos options :
- Écrire des modèles individuels. Si vous avez peu de variations, créez simplement des fichiers
.sqlséparés. - Utiliser dbt_codegen. Générez les fichiers YAML et SQL une fois, puis maintenez-les manuellement.
- Prétraitement externe. Utilisez un script Python pour générer les fichiers
.sqlavant l’exécution de dbt.
Dans la plupart des cas, l’option 1 est la bonne réponse. Résistez à la tentation de sur-ingénierer.
Pré-opérations et post-opérations
Les pre_operations et post_operations de Dataform deviennent des hooks dbt :
Dataform :
config { type: "table", pre_operations: ["DELETE FROM ${self()} WHERE date < DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY)"]}dbt :
{{ config( materialized='table', pre_hook="DELETE FROM {{ this }} WHERE date < DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY)") }}Pour les pré/post opérations complexes couvrant plusieurs instructions, envisagez des matérialisations personnalisées.
Valider la migration
Ne basculez pas tout d’un coup. Exécutez les systèmes en parallèle et validez minutieusement.
Exécution en parallèle
Gardez Dataform en fonctionnement pendant la mise en place de dbt. Configurez dbt pour écrire dans un dataset séparé :
models: my_project: +schema: dbt_migration_validationExécutez les deux systèmes sur les mêmes données sources.
Requêtes de comparaison
Pour chaque modèle migré, lancez une validation :
-- Comparaison du nombre de lignesSELECT 'dataform' AS source, COUNT(*) AS row_count FROM dataform_dataset.model_nameUNION ALLSELECT 'dbt' AS source, COUNT(*) AS row_count FROM dbt_migration_validation.model_name;
-- Comparaison au niveau des colonnes pour les métriques clésSELECT ABS(d.total_revenue - t.total_revenue) AS revenue_diff, ABS(d.order_count - t.order_count) AS order_diffFROM (SELECT SUM(revenue) AS total_revenue, COUNT(*) AS order_count FROM dataform_dataset.orders) dCROSS JOIN (SELECT SUM(revenue) AS total_revenue, COUNT(*) AS order_count FROM dbt_migration_validation.orders) t;Tests de régression des pipelines ML
Si des modèles de machine learning consomment vos sorties de transformation, testez minutieusement. L’histoire d’avertissement de la recherche : une équipe a découvert que le comportement du templating JavaScript et Jinja différait de manières qui cassaient le réentraînement des modèles. La précision numérique, le traitement des nulls et le formatage des timestamps peuvent tous diverger de manière subtile.
Exécutez votre pipeline d’entraînement ML sur les sorties dbt avant de déclarer victoire. Comparez les métriques de performance des modèles, pas seulement le nombre de lignes.
Le calendrier réaliste
En fonction de la complexité du projet, voici à quoi vous attendre :
| Taille du projet | Délai | Effort principal |
|---|---|---|
| Petit (~20 modèles) | 1-2 semaines | Conversion essentiellement automatisée |
| Moyen (50-100 modèles) | 2-4 semaines | Conversion des macros, mise en place des tests |
| Grand (100+ modèles, macros complexes) | 2-3 mois | Réécriture JavaScript, validation |
| Entreprise avec dépendances ML | 3-6 mois | Exécution en parallèle, validation par les parties prenantes |
Ce qui allonge les délais :
- Des includes JavaScript complexes nécessitant une réécriture manuelle
- Des stratégies incrémentales personnalisées au-delà du simple merge
- Des pipelines ML nécessitant une validation de régression
- Les processus d’approbation des parties prenantes
- Les exigences d’exécution en parallèle pour la conformité
Outils disponibles
Deux outils open source peuvent accélérer la migration :
ra_dbt_to_dataform - Malgré le nom suggérant la direction inverse, la même équipe fournit des patterns pour la conversion. Utilise GPT-4 pour la conversion de macros complexes.
dataform-to-dbt - Outil Node.js qui gère les refs, assertions et matérialisations en vue. Lancez avec npx dataform-to-dbt. Limites : ne gère pas les includes JavaScript, les modèles incrémentaux ni les pré-opérations complexes.
Ces outils couvrent environ 60-70 % d’un projet typique. Prévoyez que le reste sera du travail manuel.
Prendre la décision
La migration de Dataform vers dbt n’est ni universellement bonne ni universellement mauvaise. Tout dépend de votre situation spécifique.
Migrez quand vous passez au multi-warehouse, avez besoin de l’écosystème de packages, ou voulez des fonctionnalités entreprise comme le semantic layer et Mesh. L’investissement de 2-3 mois porte ses fruits en maintenance réduite et capacités élargies.
Restez en place quand votre projet Dataform est stable, que vous êtes uniquement sur BigQuery sans intention de changer, et qu’il ne vous manque pas de fonctionnalités. Le principe du “si ça marche, on ne touche pas” s’applique.
Réévaluez le calendrier quand vous avez de la génération JavaScript complexe, des dépendances avec des pipelines ML, ou des parties prenantes exigeant une exécution en parallèle prolongée. L’estimation de deux mois devient six mois dans ces scénarios.
Les outils transforment le SQL de manière identique au niveau du warehouse. Ce qui diffère, c’est l’écosystème, le modèle commercial et les implications de carrière. Faites votre choix en fonction de la trajectoire de votre organisation, pas de la syntaxe qui vous paraît plus élégante.