Mendalami dbt (data build tool) sebagai standar transformation layer: model SQL yang testable dan versionable, dbt test untuk data quality, source freshness, macros untuk eliminasi duplikasi, integrasi version control, dan praktik membangun data mart end-to-end

Di episode 6 kita membangun transformasi dengan SQL mentah di dalam psql. Itu bekerja untuk satu query — tapi apa yang terjadi ketika transformasi berjumlah ratusan, harus dijalankan berurutan, dan tim lain ikut mengubahnya? Jawabannya adalah dbt (data build tool): tool yang mengubah transformasi warehouse menjadi software engineering — dengan model, test, dokumentasi, dan version control. Di episode ini kita bangun project dbt dari nol sampai data mart siap dikonsumsi.
dbt bekerja dengan prinsip sederhana: setiap file SQL di folder models/ adalah sebuah model yang menjadi tabel/view di warehouse. Model memakai bahasa SQL biasa, tapi dengan dua keajaiban: ref() dan sources.
Alih-alih menulis nama tabel hard-coded, gunakan {{ ref('model') }} untuk merujuk model lain. dbt lalu otomatis menentukan urutan eksekusi berdasarkan dependency:
SELECT
order_id,
customer_id,
amount,
status,
created_at
FROM {{ ref('raw_orders') }}
WHERE status != 'cancelled'Deklarasikan sumber di sources.yml, sehingga tabel dari ingestion (misalnya hasil Airbyte) dipisah dari model internal:
sources:
- name: raw
database: de_lab
schema: public
tables:
- name: raw_orders
loaded_at_field: ingested_at
freshness:
warn_after: { count: 12, period: hour }
error_after: { count: 24, period: hour }Definisi freshness di atas membuat dbt mengukur umur data tiap kali dbt build dijalankan — memperingatkan jika raw_orders tidak diperbarui lebih dari 12 jam.
Inisialisasi project dan sambungkan ke PostgreSQL:
pip install dbt-postgres
mkdir ~/de-lab/dbt_project && cd ~/de-lab/dbt_project
dbt init my_martmy_mart:
target: dev
outputs:
dev:
type: postgres
host: localhost
port: 5432
user: de
password: secret
dbname: de_lab
schema: analyticsStruktur project dbt standar:
my_mart/
├── dbt_project.yml
├── models/
│ ├── staging/ # stg_* — pembersihan dari raw
│ └── marts/ # data mart yang dikonsumsi analyst
└── snapshots/ # SCD type 2 otomatisPola dbt modern: staging → intermediate → marts. Setiap lapisan punya tujuan yang jelas.
SELECT
order_id,
customer_id,
order_date,
SUM(amount) AS total_amount
FROM {{ ref('stg_orders') }}
GROUP BY order_id, customer_id, order_dateSELECT
c.customer_id,
c.customer_name,
c.segment,
COUNT(f.order_id) AS total_orders,
SUM(f.total_amount) AS total_revenue
FROM {{ ref('stg_customers') }} c
LEFT JOIN {{ ref('fct_orders') }} f USING (customer_id)
GROUP BY c.customer_id, c.customer_name, c.segmentSalah satu nilai terbesar dbt: test didefinisikan berdampingan dengan model, bukan sebagai script terpisah. Test bawaan (not_null, unique, accepted_values) dideklarasikan di YAML:
models:
- name: fct_orders
description: "Fakta pesanan per order"
columns:
- name: order_id
tests:
- unique
- not_null
- name: total_amount
tests:
- not_nullTest kustom ditulis sebagai query yang harus mengembalikan baris kosong:
SELECT *
FROM {{ ref('fct_orders') }}
WHERE total_amount < 0Jika ada total negatif, dbt test gagal dan pipeline dianggap rusak. Data quality ini kita dalami di episode 14.
Tip
Gunakan dbt build (bukan dbt run) untuk satu perintah yang menjalankan model DAN test-nya bersamaan. Pola "model dulu, test menempel" ini adalah dasar budaya data quality modern.
Saat pola yang sama berulang, ekstrak ke macro (Jinja):
{% macro standardize_amount(col) %}
COALESCE({{ col }}, 0)::NUMERIC
{% endmacro %}
SELECT {{ standardize_amount('amount') }} AS amount
FROM {{ ref('stg_orders') }}dbt menuntut project hidup di Git. Workflow yang benar untuk tim:
dbt build --select state:modified untuk membangun hanya yang berubah.Seluruh model terdokumentasi otomatis lewat dbt docs generate, dan lineage antar model terlihat di UI dbt docs — fondasi untuk topik lineage di episode 15.
Model SQL raksasa tanpa pemisahan staging/marts. Satu query 300 baris sulit diuji dan dipakai ulang. Pecah jadi lapisan.
Hard-coded nama tabel, bukan ref(). Dependency graph rusak, urutan eksekusi salah, dan test tidak menemukan target.
Tanpa test pada model inti. unique dan not_null pada primary key fact adalah minimum mutlak.
Melewatkan source freshness. Pipeline bisa "berhasil" padahal sumber sudah mati seminggu. Definisi freshness sejak awal mencegah ini.
Di episode 7 ini kalian telah menguasai transformation layer modern:
ref() untuk dependency graph dan sources() untuk memisahkan ingestion.Di episode 8 selanjutnya kita beralih ke orkestrasi: batch pipeline & Airflow — DAG, operators, scheduling, monitoring pipeline, dan praktik membangun DAG ekstraksi → transformasi → load. Sampai jumpa di episode 8!