Introductie
Git en branches zijn in bijna elk voorgaand hoofdstuk terloops genoemd — Databricks Repos in Databricks, de adf_publish-branch in Azure Data Factory, de feature-branch-naar-staging-flow in CI/CD for Data Engineering. Dit hoofdstuk behandelt Git zelf en, belangrijker, de branchingstrategie-vraag specifiek voor data engineering: welke aanpak past bij hoe dataeams daadwerkelijk werken, en waarom dat vaak een ander antwoord oplevert dan bij traditionele softwareteams.
Git-Fundamenten: het Objectmodel in het Kort
Voor wie voornamelijk uit een SQL-achtergrond komt, is het waard even stil te staan bij wat Git onder de motorkap daadwerkelijk vastlegt, omdat het de meeste verwarrende Git-situaties verklaart. Een commit is geen "diff" — het is een volledige, onveranderlijke snapshot van de complete projectstructuur op dat moment, met een verwijzing naar zijn ouder-commit(s). Een branch is niets meer dan een beweeglijk label dat naar een specifieke commit wijst — wanneer je een nieuwe commit maakt, verschuift het label van de huidige branch automatisch mee.
Dit verklaart waarom merge en rebase verschillende geschiedenissen opleveren voor hetzelfde eindresultaat: een merge voegt een nieuwe commit toe die twee ouder-commits samenbrengt (de geschiedenis vertakt zichtbaar en komt weer samen), terwijl een rebase de commits van jouw branch een voor een herschrijft bovenop de nieuwste stand van de doelbranch (de geschiedenis blijft lineair, alsof je vanaf het begin op de nieuwste code had gewerkt). Voor data engineering-projecten, waar een lineaire, makkelijk te doorzoeken geschiedenis vaak waardevoller is dan een exacte weergave van wanneer welke branch bestond, geven veel teams de voorkeur aan rebase voor feature branches en reserveren ze merge-commits voor het samenvoegen naar main zelf.
Branchingstrategieën Vergeleken
Drie strategieën domineren de discussie, elk met een andere balans tussen structuur en snelheid.
Git Flow gebruikt langlevende develop- en main-branches, met aparte feature/, release/ en hotfix/-branches die volgens een strikt patroon samenkomen. Het biedt veel structuur en expliciete release-momenten, maar de overhead is aanzienlijk — voor de meeste data engineering-teams, die zelden discrete "releases" uitbrengen zoals een versioned softwareproduct, is dit vaak meer proces dan nodig.
GitHub Flow is aanzienlijk eenvoudiger: één langlevende main-branch, korte feature branches die direct vanuit main worden getakt en er via een pull request weer in worden gemerged, met continue deployment na elke merge. Dit past goed bij teams die vaak, in kleine stappen, naar productie deployen — precies het patroon uit de CI/CD-flow in CI/CD for Data Engineering.
Trunk-based development gaat nog een stap verder: extreem korte of zelfs geen feature branches, met wijzigingen die binnen uren (niet dagen) terug in main landen, vaak achter feature flags voor grotere wijzigingen die nog niet volledig klaar zijn. Dit minimaliseert merge-conflicten door simpelweg de tijd te beperken waarin twee branches uit elkaar kunnen groeien.
Waarom Dataeams Vaak voor Eenvoudigere Flows Kiezen
Data engineering-projecten hebben twee kenmerken die de balans richting eenvoudigere strategieën (GitHub Flow, trunk-based) doen doorslaan, vergeleken met bijvoorbeeld een groot softwareproduct met discrete versienummers.
Ten eerste: dbt-modellen en SQL-bestanden conflicteren vervelender dan de meeste applicatiecode. Een gegenereerd of sterk geherformatteerd SQL-bestand waar twee feature branches allebei aan hebben gewerkt, levert vaak merge-conflicten op die niet regel-voor-regel op te lossen zijn zonder de onderliggende businesslogica van beide wijzigingen te begrijpen — anders dan bijvoorbeeld twee losse functies in verschillende delen van een applicatiebestand, die Git meestal probleemloos automatisch samenvoegt. Kortere branches met snellere merges beperken direct hoe lang zulke conflicten de kans krijgen om te ontstaan.
Ten tweede: data pipelines hebben zelden discrete "releases" in de zin van een softwareproduct. Er is geen versie 2.0 van een dataplatform die gebruikers bewust installeren — er is een continu stromende, incrementeel verbeterde reeks modellen. Git Flow's release-branches lossen een probleem op (het coördineren van een versioned, installeerbaar product) dat de meeste dataplatformen simpelweg niet hebben.
De praktische vuistregel: begin met GitHub Flow als standaard voor een dataplatform-repository, en overweeg Git Flow specifiek alleen voor herbruikbare, versioned artefacten zoals een gedeeld dbt-package dat door meerdere, onafhankelijke projecten wordt geïmporteerd — daar geldt wél een traditioneel versioning-argument.
Branches en Omgevingen: het Algemene Patroon
CI/CD for Data Engineering toonde de feature-branch-naar-staging-naar-productie-flow al concreet; hier het onderliggende, platformonafhankelijke principe dat elk platformspecifiek voorbeeld in dit handbook — Databricks Repos, ADF's collaboration branch, Fabric's workspace-Git-koppeling — deelt: een branch (of een specifieke stand daarvan) correspondeert met een omgeving, en het promoveren van een wijziging naar een volgende omgeving is letterlijk het mergen van die branch naar de volgende:
feature/add-churn-model → main (= staging-omgeving) → release-tag (= productie)
Wat per platform verschilt, is puur de mechaniek van hoe die merge daadwerkelijk een omgeving bijwerkt — dbt-targets, ADF's adf_publish-ARM-templates, Databricks' workspace-synchronisatie — niet het onderliggende Git-principe zelf.
Merge-Conflicten in SQL/dbt-Projecten Beperken
Naast kortere branches helpen een paar concrete gewoontes om de vervelende SQL-merge-conflicten hierboven te minimaliseren. Kleinere, gerichte pull requests — één model of één logisch samenhangende set wijzigingen per PR, in plaats van een grote PR die tien ongerelateerde modellen tegelijk aanraakt — verkleinen zowel de kans op conflicten als de moeite om ze op te lossen wanneer ze toch optreden. Consistente SQL-formattering (via sqlfluff format uit CI/CD for Data Engineering, automatisch toegepast vóór commit) voorkomt dat triviale opmaakverschillen — spaties, hoofdlettergebruik — zich vermengen met daadwerkelijke logica-conflicten en die onnodig lastiger maken om te doorzien.
Commit Message Conventies
Conventional Commits (feat:, fix:, docs:, refactor:) is een lichte, veelgebruikte conventie die commit-berichten machineleesbaar maakt, wat automatische changelog-generatie en semantic versioning mogelijk maakt — vooral relevant voor gedeelde dbt-packages die andere projecten als dependency importeren, zoals genoemd bij de packages-sectie in dbt Fundamentals:
feat(orders): voeg incrementele laadstrategie toe aan fct_orders
fix(customers): corrigeer dubbele klant-ID's door hoofdletterongevoelige match
docs: leg grain-conventie uit in silver-modellen
Voor een intern dataplatform zonder gedeelde packages is dit minder kritiek dan bij open-source of gedeelde libraries, maar de discipline van een duidelijk, gestructureerd commit-bericht — wat is er veranderd, en waarom — blijft waardevol voor iedereen die maanden later probeert te achterhalen waarom een specifieke wijziging is gemaakt, zonder de oorspronkelijke auteur te hoeven vragen.
Branch Protection: main Beschermen tegen Zichzelf
Een productie-main-branch zonder bescherming staat toe dat iemand per ongeluk direct pusht, CI overslaat, of een PR merget zonder review — precies de risico's die de zorgvuldig opgebouwde CI/CD-flow uit CI/CD for Data Engineering ondermijnen. Branch protection rules (op GitHub, GitLab, Azure DevOps vergelijkbaar beschikbaar) dwingen af dat:
- Directe pushes naar
mainniet zijn toegestaan — alles moet via een pull request. - Een pull request pas gemerged kan worden nadat de CI-checks (lint, Slim CI, tests) succesvol zijn afgerond.
- Minstens één andere engineer de wijziging heeft goedgekeurd vóór een merge.
Dit is geen bureaucratie om zichzelf — het is de technische afdwinging van precies de "geen enkele wijziging bereikt productie zonder review en groen licht van CI"-garantie die de rest van de CI/CD-flow probeert te bieden.
Een Merge-Conflict Ontrafelen: een Werkvoorbeeld
Concreet, zodat het geen abstract probleem blijft: twee engineers werken parallel aan models/gold/fct_orders.sql. Engineer A voegt een kolom toe voor belastingberekening; engineer B wijzigt onafhankelijk de filterlogica voor geannuleerde orders. Bij het mergen toont Git:
<<<<<<< HEAD
SELECT
order_id,
amount,
amount * 0.21 AS tax_amount,
status
FROM {{ ref('stg_orders') }}
WHERE status != 'cancelled'
=======
SELECT
order_id,
amount,
status
FROM {{ ref('stg_orders') }}
WHERE status NOT IN ('cancelled', 'test')
>>>>>>> feature/exclude-test-orders
Dit is precies het scenario waar automatische samenvoeging faalt: beide wijzigingen zijn geldig en moeten allebei behouden blijven, maar Git kan niet weten dat tax_amount en de uitgebreide WHERE-clausule onafhankelijke, allebei-gewenste wijzigingen zijn. De juiste oplossing vereist begrip van beide bedoelingen, niet alleen tekstuele samenvoeging:
SELECT
order_id,
amount,
amount * 0.21 AS tax_amount,
status
FROM {{ ref('stg_orders') }}
WHERE status NOT IN ('cancelled', 'test')
Dit is exact waarom kleinere, snellere pull requests er in de praktijk toe doen: hoe korter engineer A en B's branches naast elkaar bestonden vóór het mergen, hoe kleiner de kans dat hun wijzigingen elkaar op deze manier kruisten in de eerste plaats.
Monorepo versus Polyrepo voor Dataplatformen
Een vraag die vroeg in een project moet worden beslist: horen alle dbt-modellen, Python-scripts en infrastructuurcode van een dataplatform in één repository (monorepo), of verspreid over meerdere, aparte repositories per team of domein (polyrepo)?
Een monorepo maakt cross-domein wijzigingen eenvoudiger (één pull request kan tegelijk een silver-model en de gold-modellen die ervan afhangen aanpassen), geeft iedereen zicht op de volledige codebase, en vereenvoudigt Slim CI uit CI/CD for Data Engineering — één manifest.json dekt het hele project. De keerzijde: naarmate een organisatie groeit, kan een monorepo een knelpunt worden voor onafhankelijke team-releasecycli, en toegangscontrole op repository-niveau wordt grover (lastiger om het finance-team alleen toegang te geven tot finance-modellen als alles in dezelfde repo staat).
Een polyrepo-aanpak, met een aparte repository per domein of team (vergelijkbaar met hoe Unity Catalog-catalogi in Databricks governance per domein scheiden), geeft teams meer autonomie en fijnmaziger toegangsbeheer, ten koste van meer overhead bij cross-domein wijzigingen en een lastiger te onderhouden, gefragmenteerde Slim CI-configuratie over meerdere repositories heen.
De praktische vuistregel: begin met een monorepo voor een dataplatform tot de organisatie een omvang bereikt (meerdere onafhankelijke teams met eigen releasecycli en strikte onderlinge toegangsscheiding) die de overhead van polyrepo daadwerkelijk rechtvaardigt — vroegtijdig opsplitsen voegt vooral coördinatiekosten toe zonder een navenant voordeel.
Best Practices
- Kies GitHub Flow of trunk-based development als standaard voor een dataplatform-repository, en reserveer Git Flow specifiek voor gedeelde, versioned dbt-packages.
- Houd feature branches kort-levend (dagen, niet weken) om SQL-merge-conflicten te minimaliseren door simpelweg de tijd te beperken waarin branches uit elkaar groeien.
- Automatiseer SQL-formattering vóór commit, zodat opmaakverschillen nooit vermengd raken met daadwerkelijke logicawijzigingen in een merge-conflict.
- Stel branch protection in op
mainvanaf het eerste moment dat een dataplatform-repository productie voedt, niet pas na het eerste incident. - Gebruik kleine, gerichte pull requests — één logisch samenhangende wijziging per PR is makkelijker te reviewen én te mergen dan een grote, verzamelde batch.
- Begin met een monorepo voor een nieuw dataplatform, en overweeg polyrepo pas expliciet zodra meerdere onafhankelijke teams met eigen releasecycli en strikte toegangsscheiding daar daadwerkelijk om vragen.
Veelgemaakte Fouten
- Langlevende feature branches aanhouden die weken uit
mainblijven groeien, wat de kans op pijnlijke, moeilijk oplosbare SQL-merge-conflicten aanzienlijk vergroot. - Git Flow overnemen zonder na te denken of het bij het project past — veel van de release-branch-complexiteit lost een probleem op (versioned, installeerbare releases) dat de meeste dataplatformen niet hebben.
- Geen branch protection op
main, waardoor een enkele, ongereviewde direct-push de zorgvuldig opgebouwde CI/CD-garanties omzeilt. - Enorme, verzamelde pull requests die tien ongerelateerde modellen tegelijk wijzigen, wat reviews vertraagt en conflicten moeilijker te ontrafelen maakt.
- Inconsistente SQL-formattering tussen teamleden, waardoor elke merge onnodige, puur cosmetische conflicten met zich meebrengt bovenop de echte logicawijzigingen.
- Te vroeg opsplitsen in polyrepo's vanuit een aanname over toekomstige schaal, wat vooral coördinatie-overhead toevoegt zolang de organisatie die complexiteit nog niet daadwerkelijk nodig heeft.
Performance Tips
- Houd gegenereerde of grote binaire bestanden buiten Git (via
.gitignore) — compiled dbt-artefacten, lokale virtual environments, en grote testdatasets horen niet in versiebeheer en vertragen elke clone en fetch onnodig. - Gebruik shallow clones (
git clone --depth 1) in CI-omgevingen waar de volledige geschiedenis niet nodig is, om checkout-tijd te verkorten. - Squash kleine, opeenvolgende "fix typo"-commits vóór het mergen van een feature branch, zodat de geschiedenis van
mainleesbaar blijft in plaats van vervuild te raken met ruis. - Prune verouderde, al-gemergede branches regelmatig, zowel lokaal als remote, om de repository overzichtelijk te houden naarmate een project groeit.
Interviewvragen
"Wat is het verschil tussen merge en rebase, en wanneer zou je welke gebruiken?"
Let op: merge behoudt de vertakkende geschiedenis met een expliciete merge-commit; rebase herschrijft commits bovenop de nieuwste doelbranch voor een lineaire geschiedenis — rebase vaak binnen een feature branch, merge (via PR) om terug naar main te gaan.
"Waarom kiezen veel data engineering-teams voor GitHub Flow of trunk-based development in plaats van Git Flow?" Let op: dataplatformen hebben zelden discrete, versioned releases zoals een softwareproduct, en langlevende branches vergroten de kans op lastige SQL/dbt-merge-conflicten — eenvoudigere flows sluiten beter aan bij hoe dataeams daadwerkelijk werken.
"Waarom zijn merge-conflicten in SQL/dbt-projecten vaak lastiger dan in applicatiecode?" Let op: gegenereerde of sterk gestructureerde SQL laat zich niet altijd regel-voor-regel automatisch samenvoegen zonder de onderliggende businesslogica van beide wijzigingen te begrijpen — anders dan bijvoorbeeld twee losse functies in verschillende bestandsdelen.
"Wat doet branch protection, en waarom is het nodig naast een CI/CD-pijplijn?"
Let op: het dwingt technisch af dat wijzigingen alleen via een gereviewde, CI-goedgekeurde pull request naar main kunnen — zonder deze afdwinging blijft de zorgvuldig opgebouwde CI/CD-garantie afhankelijk van discipline in plaats van techniek.
"Hoe zou je merge-conflicten in een dbt-project structureel minimaliseren?" Let op: kortere feature branches, kleinere/gerichte pull requests, en geautomatiseerde, consistente SQL-formattering vóór commit — een combinatie van procesmatige en technische maatregelen.
"Wanneer zou je voor een monorepo kiezen bij een dataplatform, en wanneer voor polyrepo?" Let op: monorepo vereenvoudigt cross-domein wijzigingen en Slim CI voor kleinere tot middelgrote teams; polyrepo geeft meer autonomie en fijnmaziger toegangscontrole naarmate een organisatie meerdere, onafhankelijke teams met eigen releasecycli krijgt — begin met monorepo tot de overhead van polyrepo daadwerkelijk gerechtvaardigd is.
Relevante Documentatie
- Git: Pro Git Book — Branching — de officiële, diepgaande uitleg van Git's objectmodel en branches.
- GitHub: About Protected Branches — configuratie van branch protection rules.
- Conventional Commits — de specificatie voor gestructureerde commit-berichten.
- dbt Labs: Git and Version Control Best Practices — dbt-specifieke aanbevelingen voor branching en samenwerking.
Samenvatting
Git's objectmodel — onveranderlijke commits, branches als beweeglijke labels — verklaart waarom merge en rebase verschillende geschiedenissen opleveren voor hetzelfde resultaat. Van de drie dominante branchingstrategieën passen GitHub Flow en trunk-based development doorgaans beter bij data engineering-teams dan Git Flow, omdat dataplatformen zelden discrete, versioned releases hebben en langlevende branches de kans op lastige SQL/dbt-merge-conflicten vergroten. Branches corresponderen met omgevingen — hetzelfde onderliggende principe dat Databricks Repos, ADF's adf_publish en Fabric's Git-integratie elk op hun eigen manier implementeren. Branch protection, kleine gerichte pull requests en consistente formattering zijn de concrete, technische maatregelen die deze principes afdwingen in plaats van aan discipline over te laten. Met Git als fundament behandeld, verschuift het volgende hoofdstuk naar een ander DevOps-bouwsteen: Docker, en hoe containerisatie past binnen data engineering-workflows.
Hulp nodig bij het bouwen van een modern dataplatform?
DataPartner365 helpt organisaties met Microsoft Fabric, Snowflake, Databricks, dbt, Azure, data-architectuur en CI/CD.
Neem contact op met DataPartner365