Wszystko, czego potrzebujesz, żeby od pierwszego dnia dowieźć produkcyjny projekt dbt

Każdy projekt dbt zaczyna się tak samo. Ktoś tworzy nowe repo, odpala dbt init i myśli: "porządną strukturę dodamy później". Mija pół roku. Projekt urósł, dołącza kolejnych trzech inżynierów, nie ma spójnej konwencji nazewnictwa, nie ma CI, nie ma walidacji metadanych i nikt tak naprawdę nie wie, które modele są bezpieczne do zmiany. Dług techniczny jest realny, a jego spłata nagle wymaga weekendu, którego nikt nie chce oddać.
Widzieliśmy ten schemat tyle razy, że przestaliśmy traktować go jako nieunikniony. Dlatego zbudowaliśmy starter kit, który od pierwszego commita ma wbudowane praktyki na poziomie enterprise. W tym artykule pokazuję, co w nim jest, po co istnieje każdy element i jak pomaga uniknąć najczęstszych pułapek podczas skalowania projektu dbt w organizacji.
Szablon jest dostępny na GitHubie. Sklonuj go, dopasuj do swojego stacku i pomiń bolesne lekcje.
Struktura folderów, która nie będzie Cię straszyć za pół roku
Pierwsza decyzja w projekcie dbt dotyczy organizacji modeli. Większość tutoriali pokazuje płaski folder models/. To działa dla dziesięciu modeli. Przy stu robi się z tego nawigacyjny koszmar.
Starter kit używa sztywnej, trzywarstwowej struktury:
project/
models/
staging/ # warstwa zgodna ze źródłem: 1 model na 1 tabelę źródłową
intermediate/ # reużywalna logika biznesowa
marts/ # finalne fakty i wymiary konsumowane przez BI
macros/
analyses/codegen/
tests/unit/
seeds/
snapshots/
Warstwa staging/ ma być wierna źródłu. Każdy model mapuje się 1:1 na tabelę upstream, robi lekkie czyszczenie (casty typów, rename kolumn, filtrowanie usuniętych rekordów) i nic więcej. Tutaj nie ma logiki biznesowej.
Warstwa intermediate/ to miejsce, gdzie logika biznesowa staje się reużywalna. Jeśli łapiesz się na tym, że ten sam join piszesz w trzech różnych martach, to znaczy, że powinien żyć w intermediate/. Ta warstwa bywa pomijana w mniejszych projektach i właśnie wtedy zaczyna jej najbardziej brakować.
Warstwa marts/ to publiczne API Twojej hurtowni. To modele, których dotykają analitycy i narzędzia BI. Powinny być stabilne, dobrze udokumentowane i przetestowane.
Taki podział jest ważny dla onboardingu. Nowy inżynier widzi strukturę folderów i od razu rozumie, jak płynie dane przez system. Ta czytelność ma realną wartość.
Pamiętaj, że to podstawowy, minimalny podział. Prawdopodobnie dopasujesz go do biznesu. Na przykład: każdemu źródłu możesz dać osobny folder w warstwie staging/.
Kontrakty metadanych
To element starter kita, który wzbudza najsilniejsze reakcje. Gdy mówię, że każdy model powinien mieć w configu contract.enforced: true, a do tego data_type i description dla każdej kolumny, pierwsza odpowiedź zwykle brzmi: "to masa narzutu".
To nie jest narzut. To konstrukcja nośna.
Co się dzieje bez kontraktów? Inżynier zmienia nazwę kolumny w modelu staging, marty zależne albo failują po cichu, albo - co gorsza - zaczynają zwracać nulle. Trzy dni później ktoś zauważa, że liczby się nie zgadzają, a znalezienie przyczyny zajmuje pół dnia.
Z contract.enforced: true dbt odmówi zbudowania modelu, jeśli realny schemat nie zgadza się z zadeklarowanym. Błąd jest głośny, natychmiastowy i da się go naprawić.
# stg_example_orders.yml
models:
- name: stg_example_orders
config:
contract:
enforced: true
columns:
- name: order_id
description: "Unikalny identyfikator zamówienia."
data_type: varchar
- name: order_date
description: "Data złożenia zamówienia."
data_type: date
Ale starter kit idzie dalej. Skrypt walidacyjny w Pydantic uruchamia się w CI i sprawdza, że:
- każdy plik
.sqlwmodels/ma odpowiadający mu plik.yml - każdy plik
.ymlmaversion: 2 - każda kolumna ma
name,descriptionidata_type contract.enforcedjest ustawione natrue
Jeśli którykolwiek z tych warunków nie przejdzie, pipeline CI failuje. Nie da się zmergować modelu bez dokumentacji. To rodzaj guardraila, który irytuje w pierwszym tygodniu i staje się niewidzialny w drugim miesiącu, bo wszyscy po prostu robią to automatycznie.
Linting i pre-commit: łap problemy zanim trafią do repo
Code review powinien dotyczyć logiki, a nie formatowania. Dyskusje o stylu w PR-ach to strata czasu i rozpraszają reviewerów, przez co łatwiej przeoczyć realne problemy.
Starter kit używa hooków pre-commit, żeby wymusić spójność zanim kod w ogóle trafi do pull requesta. W pliku .pre-commit-config.yaml spinamy cztery narzędzia:
SQLFluff pilnuje formatowania i stylu SQL. Jest skonfigurowany pod dialekt Snowflake i dbt templater, więc rozumie Jinja i potrafi lintować realne modele dbt, zamiast traktować {{ ref('...') }} jako błąd składni.
Ruff pilnuje Pythona. Towarzyszący pyproject.toml zarządza projektem jako paczką Pythona z użyciem uv, co daje powtarzalne dependency management na devkach i w CI.
yamllint łapie błędny YAML zanim spowoduje dziwne błędy parsowania dbt. Brak spacji po dwukropku w pliku schema to bug, który potrafi zjeść 20 minut.
gitleaks skanuje pod kątem sekretów. Credentiale do hurtowni i klucze API nie mogą trafić do repo. To niepodlegające dyskusji w enterprise, a automatyczne skanowanie sprawia, że nie musisz liczyć na pamięć developerów.
Uruchomienie pre-commit install po sklonowaniu repo to jednorazowy krok. Potem każdy commit jest walidowany automatycznie, zanim opuści maszynę developera.
Workflowy CI/CD, które faktycznie uruchamiają dbt
Pre-commit łapie lokalne problemy. CI łapie wszystko, co się prześlizgnie i wymusza standardy dla całego zespołu, także dla osób, które nie mają skonfigurowanego pre-commit lokalnie.
Starter kit zawiera cztery workflowy GitHub Actions:
lint.yml uruchamia ruff, SQLFluff i yamllint na każdym pull requeście. Szybki feedback, bez potrzeby dostępu do hurtowni.
validate-model-metadata.yml uruchamia skrypt walidujący Pydantic. To osobny job, bo to nie jest lintowanie - sprawdzamy semantyczną poprawność dokumentacji, a nie styl kodu.
dbt-build.yml uruchamia dbt debug, a potem dbt build na środowisku CI w Snowflake. To workflow, który naprawdę kompiluje i wykonuje modele, potwierdzając, że build działa. Credentiale są przekazywane przez zmienne środowiskowe, a dołączony profiles/profiles.yml pokazuje dokładnie, jakich zmiennych oczekujemy.
gitleaks.yml skanuje całą historię repo pod kątem sekretów. Działa na push do main oraz na PR-ach, bo lepiej złapać sekret na branchu niż po merge.
Razem te cztery workflowy sprawiają, że nic nie trafi na main, jeśli SQL się nie lintuje, YAML jest niepoprawny, metadane są niekompletne albo build dbt nie przechodzi na prawdziwej hurtowni. To mocna gwarancja.
Dwa makra, których potrzebuje każdy projekt
Ze wszystkich elementów starter kita te dwa makra najczęściej widzę pominięte u zespołów, które później tego żałują.
generate_schema_name kontroluje, jak dbt tworzy nazwy schematów w hurtowni. Bez tego makra dbt używa defaultu, przez co developer budujący na swoim środowisku może przypadkiem wejść w kolizję z produkcją, jeśli zmienne środowiskowe nie są ustawione idealnie. Makro w starter kicie wymusza prostą konwencję: na produkcji modele lądują w skonfigurowanym schemacie, a na devie w schemacie z prefixem per developer, tak żeby dbt build na devie nigdy nie dotykał danych produkcyjnych.
{% macro generate_schema_name(custom_schema_name, node) %}
{#
Production:
- Use folder-level +schema (custom_schema_name) when defined
- Otherwise fall back to target.schema (e.g. dbt_analytics)
Non-prod (dev, ci, etc.):
- Always use target.schema from the profile (e.g. dbt_joachimhodana),
so everything builds into your personal dataset.
#}
{% if target.name in ['prod', 'dev', 'qa', 'ci'] %}
{{ custom_schema_name or target.schema }}
{% else %}
{{ target.schema }}
{% endif %}
{% endmacro %}query_tag ustawia query tag w Snowflake dla każdego zapytania uruchamianego przez dbt. Dzięki temu w historii zapytań w Snowflake widzisz, który model dbt odpalił query, na jakim środowisku i z jakiej warstwy. To obserwowalność, z której będziesz korzystać non stop podczas debugowania performance albo rozliczania kosztów per warstwa.
Makro jest podpinane jako pre-hook w dbt_project.yml na poziomie warstw, więc staging taguje się inaczej niż marts, bez per-model konfiguracji.
Strategia testów: unit testy i dbt-expectations
Domyślne testy dbt - not_null i unique - to punkt startowy, a nie strategia. Złapią oczywiste problemy jakościowe, ale ominą subtelniejsze: błędne kalkulacje, nieoczekiwane rozkłady, rekordy, które powinny istnieć, ale ich nie ma.
Starter kit dodaje dbt-expectations jako zależność. To biblioteka, która przenosi wiele wzorców Great Expectations do YAML-a dbt. Możesz asercjami sprawdzać zakresy wartości, minimalny row count albo dopasowanie do regexa.
- name: order_amount
tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
max_value: 1000000
W starter kicie jest też przykład unit testu. Unit testy w dbt pozwalają testować logikę modelu w izolacji: podajesz mock input i asercjami sprawdzasz output. To szczególnie wartościowe dla intermediate modeli ze złożoną logiką - możesz testować edge case'y bez potrzeby pełnego odświeżenia danych.
Pakiety, które warto dodać od pierwszego dnia
Cztery pakiety są skonfigurowane w project/packages.yml:
dbt-labs/codegen generuje pliki YAML schema z istniejących modeli. Gdy przejmujesz legacy projekt bez dokumentacji, to najszybszy sposób, żeby zrobić baseline i potem go wzbogacać.
dbt-labs/dbt_utils to zestaw praktycznych utili SQL: generowanie surrogate key, date spine, pivot helpers. Użyjesz tego szybciej, niż myślisz.
calogica/dbt_expectations było omówione wyżej - zaawansowane testy jakościowe wykraczające poza built-in testy.
Matts52/dbt_orphan to pakiet, o którym większość zespołów nie słyszała. Identyfikuje i opcjonalnie usuwa tabele w hurtowni, którymi dbt już nie zarządza - śmieci po rename albo usunięciu modeli. Starter kit ma zakomentowany hook on-run-end pokazujący, jak to podpiąć. Włączenie na produkcji to jedna linijka.
Pobierz szablon
Sklonuj repo i dostaniesz działający szkielet projektu z całą konfiguracją gotową do odpalenia:
git clone https://github.com/lortechsolutions/dbt-starter-kit
cd dbt-starter-kit
uv sync
cd project
dbt debug
Zanim zaczniesz budować, są cztery rzeczy do dopasowania: zaktualizuj CODEOWNERS o właściwe handle GitHub, uzupełnij credentiale Snowflake w zmiennych środowiskowych oczekiwanych przez profiles.yml i zmień nazwę projektu dbt w dbt_project.yml.
Cała reszta - struktura folderów, workflowy CI, makra, skrypt walidacji, pre-commit hooki - jest gotowa do użycia bez zmian.
Celem tego szablonu nie jest bycie preskryptywnym w każdym detalu. Chodzi o to, żeby wyeliminować decyzje, które każdy enterprise projekt dbt i tak w końcu podejmuje w ten sam sposób, i żeby dobre domyślne ustawienia stały się automatyczne, a nie aspiracyjne.
Joachim Hodana - Software & Data Engineer
Enterprise dbt Project Setup: A Starter Kit That Scales został pierwotnie opublikowany w Lortech Solutions Blog na Medium, gdzie rozmowa trwa dalej dzięki podświetleniom i odpowiedziom czytelników.


