Model sudah dibangun, teruji, terdokumentasi, dan metriknya terdefinisi tunggal. Nilai seluruh pekerjaan tersebut baru terwujud ketika alat business intelligence membacanya: dashboard Metabase, laporan Tableau, hingga spreadsheet tim keuangan. Integrasi ini punya dua sisi yang sama pentingnya — bagaimana alat BI terhubung ke model, dan siapa yang boleh melihat baris mana. Chapter ini membahas kedua sisi tersebut, lalu mensimulasikan mekanisme kuncinya: row-level security.
Jalur Koneksi Alat BI ke Model #
Alat BI tidak membaca berkas SQL dbt; alat BI membaca tabel hasil model di warehouse melalui antarmuka SQL. Tiga jalur yang lazim: connector bawaan per warehouse, standar ODBC/JDBC yang didukung hampir semua alat, dan API semantic layer yang memanggil metrik bernama dari Chapter Semantic Layer tanpa SQL sama sekali:
| Jalur koneksi | Contoh pemakaian | Cocok untuk |
|---|---|---|
| connector bawaan warehouse | driver natif BigQuery atau Snowflake | performa terbaik pada satu warehouse |
| ODBC/JDBC | driver ODBC DuckDB, driver JDBC PostgreSQL | alat BI mana pun yang mendukung standar |
| API semantic layer | metrik bernama dari dbt | laporan yang menuntut definisi metrik tunggal |
Sebagai contoh konkret, DuckDB menyediakan driver ODBC sehingga alat BI mana pun yang mendukung standar tersebut dapat membaca database — panduan pemasangannya ada di dokumentasi DuckDB .
Jalur koneksi menentukan apa yang terlihat, jadi aturan eksposur perlu ditetapkan sejak awal mengikuti lapisan dari Chapter Model Staging, Intermediate, dan Marts:
| Lapisan model | Isi | Diekspos ke alat BI |
|---|---|---|
| staging | rename dan cleansing, satu-ke-satu dengan sumber | tidak |
| intermediate | join dan logika antar-model | tidak |
| marts | agregat siap konsumsi per departemen | ya |
Alasannya praktis: tabel staging ikut berubah setiap kali sistem sumber berganti, sedangkan mart dijaga stabil sebagai kontrak untuk pembaca laporan. Mengekspos staging ke alat BI berarti setiap perubahan sumber langsung merusak dashboard; mengekspos mart berarti perubahan sumber diserap pipeline lebih dulu.
Filter Dashboard Bukan Keamanan #
Saat tim cabang mulai memakai dashboard, cara yang paling cepat terpikirkan adalah memasang filter di UI: analyst Jakarta hanya melihat kota Jakarta. Pendekatan tersebut rapuh di tiga tempat:
- Dashboard baru yang lupa memasang filter langsung membocorkan seluruh tabel.
- Pengguna dengan akses SQL langsung membaca tabel penuh karena filter hanya hidup di tampilan.
- Logika akses tersebar di setiap dashboard sehingga tidak bisa diaudit.
Pola yang benar adalah menegakkan hak akses di lapisan data — kelanjutan langsung dari Chapter Role-Based Access Control: RBAC memutuskan tabel dan operasi apa yang boleh diakses suatu role, sedangkan row-level security memutuskan baris mana yang terlihat. Alat BI adalah pintu masuk bagi banyak pembaca sekaligus; pintu tersebut hanya aman bila kebijakan aksesnya menempel pada data.
Simulasi Row-Level Security #
Warehouse client-server umumnya menyediakan policy row-level security bawaan yang dievaluasi di dalam engine. DuckDB bersifat embedded tanpa policy server-side, jadi simulasi berikut menempatkan penegakan pada satu fungsi serving — pola yang prinsipnya sama: setiap query lewat satu pintu, dan predikat hak akses disuntikkan sebelum SQL menyentuh tabel. Docstring, import, dan pembukaan koneksi con dipotong dari tampilan; bagian intinya bekerja atas mart_penjualan_kota — hasil model dbt TokoKita — berisi enam baris agregat tiga kota:
con.execute("""
CREATE TABLE mart_penjualan_kota (
kota VARCHAR, bulan VARCHAR, gmv BIGINT, jumlah_pesanan BIGINT
);
INSERT INTO mart_penjualan_kota VALUES
('Jakarta', '2026-07', 91000000, 1210),
('Jakarta', '2026-08', 87000000, 1145),
('Bandung', '2026-07', 42000000, 690),
('Bandung', '2026-08', 45500000, 712),
('Surabaya', '2026-07', 53000000, 803),
('Surabaya', '2026-08', 50500000, 788);
""")
# matriks role -> predikat, seperti matriks role-permission RBAC
HAK = {
"analyst_nasional": "TRUE",
"analyst_jakarta": "kota = 'Jakarta'",
"store_partner": "FALSE",
}
def query_dengan_hak(role, kolom="kota, bulan, gmv"):
"""Serving selalu lewat sini: predikat role tidak bisa dilupakan."""
sql = f"SELECT {kolom} FROM mart_penjualan_kota WHERE {HAK[role]}"
return con.execute(sql).fetchall()for role in HAK:
baris = query_dengan_hak(role)
total = sum(baris[i][2] for i in range(len(baris))) if baris else 0
print(f"role={role:<18} baris={len(baris):<3} total_gmv={total:>10,}")
for baris_satu in baris[:2]:
print(" ", baris_satu)
print()
print("dashboard yang sama, tiga jawaban berbeda — inilah inti RLS")Output yang Dihasilkan:
role=analyst_nasional baris=6 total_gmv=369,000,000
('Jakarta', '2026-07', 91000000)
('Jakarta', '2026-08', 87000000)
role=analyst_jakarta baris=2 total_gmv=178,000,000
('Jakarta', '2026-07', 91000000)
('Jakarta', '2026-08', 87000000)
role=store_partner baris=0 total_gmv= 0
dashboard yang sama, tiga jawaban berbeda — inilah inti RLSMari kita telaah bagian-bagian penting skrip di atas:
- Blok pertama, baris 14-19 (matriks
HAK): setiap role dipetakan ke satu predikat SQL —analyst_nasionalmelihat semua baris,analyst_jakartahanya baris Jakarta, danstore_partnermendapatFALSEalias tidak melihat satu baris pun. - Blok pertama, baris 21-24 (
query_dengan_hak): satu-satunya pintu menuju tabel; predikat role disuntikkan ke klausaWHEREsebelum eksekusi, sehingga pemanggil fungsi tidak bisa melupakannya. - Blok kedua, baris 1-6 (simulasi tiga pembaca): dashboard yang sama dibuka oleh tiga role berbeda; kolom
total_gmvdijumlahkan hanya dari baris yang lolos predikat masing-masing. - Blok kedua, baris 8-9 (penanda penutup): kalimat terakhir dicetak setelah seluruh role selesai diuji — rangkuman bahwa satu dashboard menghasilkan tiga jawaban.
Satu dashboard, tiga jawaban: analyst nasional melihat GMV total Rp369 juta dari enam baris, analyst Jakarta melihat Rp178 juta dari dua baris, dan mitra toko tidak melihat apa pun karena memang tidak diberi hak atas data agregat. Penolakan terjadi di lapisan data, bukan di lapisan tampilan — inilah makna kalimat penutup output di atas. Pada penyajian nyata, matriks role semacam ini digantikan policy row-level security milik warehouse yang mengevaluasi identitas pengguna di dalam engine.
Predikat FALSE untuk store_partner juga bagian dari kebijakan: mitra toko memang tidak diberi hak atas data agregat internal. Mengubah kebijakan berarti mengubah satu entri matriks — seluruh jalur serving mengikuti tanpa terkecuali.
Batas tanggung jawab antar lapisan menjadi jelas di titik ini: dbt membangun tabel teruji di mart, lapisan penyajian memegang hak akses per baris, alat BI memegang tampilan. Ketiganya berubah dengan kecepatan berbeda — model mengikuti jadwal pipeline, kebijakan akses mengikuti keputusan audit, tampilan mengikuti kebutuhan produk — sehingga perubahan salah satunya tidak menyeret dua lainnya.
Hak akses kini terjaga di dekat data. Satu persoalan terakhir menyisai pada rantai konsumsi ini: ketika ratusan pengguna membuka dashboard pada jam yang sama, jawaban tetap harus datang cepat tanpa mengulang pekerjaan berat di setiap permintaan. Materi Chapter Serving Data untuk Analytics menutup section dengan pola pre-agregasinya.