Skip to content

Satu Aturan, Satu Tempat: Identitas Email dalam Skema Basis Data

Adityo Guni Waluyo

Dua constraint menjaga satu aturan identitas email. Menghapus yang redundan ternyata bukan kosmetik, melainkan menutup celah spesifikasi ganda.

Ringkasan

Migrasi ini cuma ngehapus constraint komposit tenant-email dan ganti email jadi unique, intinya satu aturan mending ditulis sekali aja. Soalnya dua spesifikasi buat aturan yang sama itu bahaya—pas aturan berubah, yang lama bisa balik nyala diam-diam. Verifikasinya aman: roundtrip migrasi bersih, autogenerate nggak nemu drift, dan insert duplikat lintas tenant tetap ditolak database.

Migrasi 0005 di DemandScope dijalankan di akhir sore, dan dua pemeriksaan yang saya jalankan menyusulnya memberi hasil yang justru lebih menarik daripada perubahan itu sendiri. Pertama, autogenerate Alembic yang membandingkan metadata ORM dengan skema database menghasilkan laporan bersih: tidak ada perbedaan yang perlu ditulis ulang [4]. Kedua, sebuah probe INSERT dengan email yang sama tapi tenant berbeda langsung ditolak di tingkat database [1].

Perubahan yang dibawa migrasi ini terdengar sepele ketika dibaca dari judulnya: hapus constraint komposit uq_users_tenant_email yang menjaga pasangan tenant dan email, lalu deklarasikan kolom email sebagai unique=True di model. Sebelum ini, aturan identitas email dijaga dua kali. Indeks unik global ix_users_email sudah menolak email ganda lintas tenant, sementara constraint komposit menjaga varian yang lebih lemah di dalam satu tenant.

Dua spesifikasi untuk satu aturan

Dugaan awal saya: menghapus constraint komposit itu tindakan kosmetik murni. Indeks global yang lebih kuat sudah ada, jadi yang komposit tidak mungkin pernah terlanggar lebih dulu. Dugaan itu benar soal perilaku saat ini, tapi salah soal apa yang sedang dikelola. Dua constraint yang menjaga satu aturan adalah dua spesifikasi untuk aturan yang sama, bukan satu spesifikasi yang ditulis dua kali.

Bayangkan refactor enam bulan ke depan: kebutuhan produk berubah, satu email kini boleh dipakai di dua tenant. Pengembang melonggarkan indeks global, dan tanpa sengaja menghidupkan kembali constraint komposit yang lupa dihapus. Database yang tadinya menolak duplikat kini menerimanya di dalam tenant, karena spesifikasi kedua yang lama diam-diam masih tertulis. Postgres mengevaluasi setiap constraint secara independen, dan pelanggaran constraint mana pun memunculkan error [1]. Aturan yang semestinya dihapus justru kembali berlaku lewat pintu belakang.

Bentuk deklarasinya juga punya konsekuensi. SQLAlchemy menyediakan unique=True untuk constraint anonim di satu kolom, dan UniqueConstraint tingkat tabel untuk constraint bernama atau multi kolom [3]. Di balik layar, Postgres otomatis membuat indeks unik untuk setiap unique constraint atau primary key, dan membuat indeks unik kedua secara manual hanya menduplikasi indeks pertama [2]. Detail kecil lain berlaku untuk constraint komposit: unique index multi kolom hanya menolak baris yang sama di semua kolomnya sekaligus [2], sehingga constraint komposit memang semantiknya berbeda dari unik global.

Verifikasi yang membuatnya bisa dipercaya

Tiga langkah yang membuktikan perubahan ini aman semuanya bisa diulang siapa pun. Roundtrip migrasi 0005 lalu 0004 berjalan tanpa sisa, dan versi turunnya membuat ulang constraint komposit persis seperti bentuk lamanya. Autogenerate menyatakan tidak ada drift antara model dan skema [4]. Terakhir, probe INSERT lintas tenant dengan email identik ditolak database, bukan ditolak validasi aplikasi [1].

Kerangka transparansi algoritmik memakai ukuran yang cocok untuk kasus ini: sifat keterlacakan sistem terpenuhi ketika faktor-faktor yang memengaruhi hasil komputasi bisa dilacak kembali [5]. Skema database adalah spesifikasi semacam itu. Selama satu aturan tertulis di satu tempat, siapa pun yang membaca definisi tabel bisa menegaskan identitas email dijaga global, tanpa menjalankan sistemnya.

Kriteria yang saya bawa pulang

Sejak migrasi ini, pertanyaan yang saya ajukan saat menambah constraint berubah. Bukan "aturan apa yang perlu dijaga", melainkan "apakah aturan ini sudah dinyatakan di tempat lain". Constraint baru layak ada kalau ia menyatakan aturan yang belum ada. Kalau isinya hanya duplikasi dari aturan yang lebih kuat, yang benar adalah menghapusnya, karena spesifikasi ganda bukan pengaman, melainkan utang yang jatuh tempo saat aturan berubah.

Sumber

  1. PostgreSQL Documentation: Constraints
  2. PostgreSQL Documentation: Unique Indexes
  3. SQLAlchemy Documentation: Defining Constraints and Indexes
  4. Alembic Documentation: Auto Generating Migrations
  5. Boche, Fono, Kutyniok: The Algorithmic Transparency Requirement (arXiv:2401.10310)

Artikel terkait