🤔 La Question Légitime
Pourquoi faire une macro quand on peut écrire directement :
-- Approche "simple" - valeur en dur
select customer_id::varchar(50) as customer_id
from {{ ref('stg_orders') }}
Au lieu de :
-- Approche macro
select customer_id::{{ resolve_conflict('customer_id') }} as customer_id
from {{ ref('stg_orders') }}
📊 Comparaison Pratique
❌ Approche Valeurs en Dur
-- models/customer_summary.sql
select customer_id::varchar(50) as customer_id from {{ ref('stg_orders') }}
-- models/customer_analysis.sql
select customer_id::varchar(50) as customer_id from {{ ref('dim_customers') }}
-- models/customer_reports.sql
select customer_id::varchar(50) as customer_id from {{ ref('raw_data') }}
Problèmes :
- 🔴 Duplication :
varchar(50)
répété 20+ fois - 🔴 Maintenance cauchemar : Changer le type = modifier 20+ fichiers
- 🔴 Erreurs humaines : Oubli d’un fichier, types incohérents
- 🔴 Pas de documentation : Pourquoi varchar(50) ?
- 🔴 Pas de traçabilité : Qui a décidé ? Quand ? Pourquoi ?
✅ Approche Macro
-- models/customer_summary.sql
select customer_id::{{ resolve_conflict('customer_id') }} from {{ ref('stg_orders') }}
-- models/customer_analysis.sql
select customer_id::{{ resolve_conflict('customer_id') }} from {{ ref('dim_customers') }}
-- models/customer_reports.sql
select customer_id::{{ resolve_conflict('customer_id') }} from {{ ref('raw_data') }}
Avantages :
- ✅ DRY Principle : Règle définie une seule fois
- ✅ Maintenance facile : Changer la macro = tout se met à jour
- ✅ Cohérence garantie : Même règle partout
- ✅ Documentation intégrée : Logs explicatifs
- ✅ Évolutivité : Règles complexes possibles
🎯 Cas Concrets : Quand Ça Devient Critique
Scénario 1 : Changement de Règle Business
Nouvelle exigence : “Les customer_id doivent passer en bigint
pour supporter l’international”
Avec valeurs en dur :
# Rechercher dans 50+ fichiers
grep -r "varchar(50)" models/
# Modifier manuellement chaque occurrence
# Risque d'en oublier une
# Tests manuels partout
Avec macro :
-- Une seule ligne à changer
{% macro resolve_conflict(column_name) %}
{% if column_name == 'customer_id' %}
{{ return('bigint') }} -- ← Seul changement nécessaire
{% endif %}
{% endmacro %}
Scénario 2 : Règles Contextuelles
Exigence : “customer_id en varchar
pour les modèles externes, bigint
pour les modèles internes”
Avec valeurs en dur :
-- Impossible sans duplication massive et erreurs
-- Comment savoir quel modèle utilise quel type ?
Avec macro :
{% macro resolve_conflict(column_name, context='internal') %}
{% if column_name == 'customer_id' %}
{% if context == 'external' %}
{{ return('varchar(50)') }}
{% else %}
{{ return('bigint') }}
{% endif %}
{% endif %}
{% endmacro %}
-- Usage
select customer_id::{{ resolve_conflict('customer_id', 'external') }}
Scénario 3 : Audit et Traçabilité
Besoin : “Qui a décidé que customer_id soit en varchar ? Quand ? Pourquoi ?”
Avec valeurs en dur :
-- Aucune trace, aucune justification
select customer_id::varchar(50) as customer_id
Avec macro :
{% macro resolve_conflict(column_name) %}
{% if column_name == 'customer_id' %}
{{ log("📋 customer_id resolved to varchar(50) - Reason: Support alphanumeric IDs from legacy system (Decision: 2024-03-15, Team: Data Architecture)", info=True) }}
{{ return('varchar(50)') }}
{% endif %}
{% endmacro %}
🏗️ Exemple Réel : Évolution d’un Projet
Phase 1 : Début Simple (Valeurs en Dur)
-- 3 modèles, types en dur, tout va bien
select customer_id::varchar(50) as customer_id
Phase 2 : Croissance (10+ Modèles)
-- 15 modèles avec varchar(50) dupliqué
-- Maintenance devient pénible mais encore gérable
Phase 3 : Complexité (50+ Modèles)
-- 50 modèles, 5 types différents par erreur :
-- - varchar(50) dans 30 modèles
-- - varchar(100) dans 15 modèles (quelqu'un a "amélioré")
-- - integer dans 3 modèles (bug)
-- - bigint dans 2 modèles (nouveau développeur)
-- = CHAOS TOTAL
Phase 3 bis : Salvation avec Macro
{% macro resolve_conflict(column_name) %}
{% if column_name == 'customer_id' %}
{{ return('varchar(50)') }} -- UNE SEULE SOURCE DE VÉRITÉ
{% endif %}
{% endmacro %}
-- Dans les 50 modèles :
select customer_id::{{ resolve_conflict('customer_id') }} as customer_id
-- = COHÉRENCE RETROUVÉE
💡 Arguments Techniques Détaillés
1. Principe DRY (Don’t Repeat Yourself)
-- ❌ Violation DRY
varchar(50) -- Répété 50 fois = 50 points de maintenance
-- ✅ Respect DRY
{{ resolve_conflict('customer_id') }} -- 1 définition, 50 utilisations
2. Single Source of Truth
-- ❌ Multiple sources
-- Fichier A: varchar(50)
-- Fichier B: varchar(100) -- Qui a raison ?
-- ✅ Source unique
-- Macro: varchar(50) -- SEULE vérité
3. Évolutivité
-- ❌ Règle fixe
varchar(50) -- Comment ajouter de la logique ?
-- ✅ Règle évolutive
{% macro resolve_conflict(column_name, source_system=none) %}
{% if column_name == 'customer_id' %}
{% if source_system == 'legacy' %}
{{ return('varchar(50)') }}
{% elif source_system == 'new_system' %}
{{ return('bigint') }}
{% else %}
{{ return('varchar(50)') }} -- default
{% endif %}
{% endif %}
{% endmacro %}
4. Testabilité
-- ❌ Valeurs en dur = pas de tests centralisés
-- Comment tester que tous les modèles utilisent le bon type ?
-- ✅ Macro = tests centralisés
-- tests/test_contract_resolution.sql
{{ audit_resolved_types(['customer_id', 'order_id']) }}
🎯 Quand Utiliser Quoi ?
Valeurs en Dur = OK pour :
- ✅ Projet tiny (< 5 modèles)
- ✅ Types jamais changeront (id technique)
- ✅ Équipe 1 personne (pas de coordination)
- ✅ Prototype rapide (jetable)
Macro = OBLIGATOIRE pour :
- ✅ Projet moyen/large (10+ modèles)
- ✅ Types métier évolutifs (customer_id, product_id)
- ✅ Équipe multiple (coordination nécessaire)
- ✅ Production long terme (maintenabilité)
- ✅ Conformité/Audit (traçabilité requise)
📋 Template de Décision
Question simple : “Si demain je dois changer le type de cette colonne, combien de fichiers je dois modifier ?”
- 1 fichier → Valeur en dur OK
- 2+ fichiers → Macro OBLIGATOIRE
Règle d’or : Dès que vous tapez le même type 2 fois, créez une macro ! 🎯
🚀 Exemple Ultra-Concret
Avant (Nightmare Mode)
-- customer_summary.sql
select customer_id::varchar(50)
-- customer_orders.sql
select customer_id::varchar(50)
-- customer_analysis.sql
select customer_id::varchar(50)
-- [45 autres fichiers avec varchar(50)]
-- 6 mois plus tard : "Il faut passer en bigint"
-- 😱 48 fichiers à modifier manuellement
Après (Dream Mode)
-- macros/types.sql
{% macro customer_id_type() %}{{ return('varchar(50)') }}{% endmacro %}
-- customer_summary.sql
select customer_id::{{ customer_id_type() }}
-- [47 autres fichiers identiques]
-- 6 mois plus tard : "Il faut passer en bigint"
-- 😍 1 ligne à changer dans la macro
{% macro customer_id_type() %}{{ return('bigint') }}{% endmacro %}
-- ✅ TOUS les modèles mis à jour automatiquement
Conclusion : La macro n’est pas de la “complexité inutile”, c’est de l’architecture future-proof ! 🎯