Kosmetik yang Memaksa Rombak Kontrak Data
Minta huruf kecil kelihatan sepele; padahal nilai enum dibandingkan literal di enforcement scope, jadi DDL, baris, konstanta, spec, dan map label pindah bareng.
Ringkasan
Kirain cuma ganti huruf kapital jadi kecil, ternyata ribet karena casing itu ngaruh ke izin akses. Jadinya harus migrasi bareng-bareng di database, backend sampai frontend biar nggak error dan user kekunci. Pengecualian cuma ALL yang tetap kapital karena itu buat superadmin.
Permintaannya datang satu baris di chat: bidang itu tolong dikecilin hurufnya, jangan PARIWISATA terus. Saya pikir lima menit beres: ganti string di beberapa tempat, commit, tutup laptop.
Dunia yang saya bayangin case-insensitive. Huruf besar-kecil kan cuma soal gaya. Lima menit itu jadi penuh mikir, karena satu fakta baru kerasa pas membuka kode: nilai bidang ini dibandingkan secara literal di enforcement scope.
Casing itu kontrak, bukan gaya
Di backend ada map bidangModules yang ngunci modul mana yang boleh dikelola tiap bidang. Key-nya konstanta string, pencocokannya exact. Nilai yang nggak dikenal balik nil, dan nil di sana artinya deny-all, fail-closed. Ganti PARIWISATA jadi pariwisata di satu tempat aja tanpa sinkron, user langganan bidang itu tiba-tiba kekunci keluar. Jadi request kosmetik ternyata minta perubahan kontrak data yang pindah bareng di banyak layer.
Hitungannya: DDL tiga kolom di database, normalisasi baris yang udah keburu tersimpan, konstanta di package scope, enum di openapi.yaml, tipe di frontend, sampai key di map label. Semua harus satu tarikan napas di satu migrasi, 000059, lengkap sama file down-nya. Dokumentasi MySQL 5.7 [1] nyatanya relevan juga di sini, bukan cuma formalitas.
Isi migrasi 000059
Di MySQL, nilai ENUM disimpan dan ditampilkan pakai lettercase persis kayak di definisi kolomnya [1]. Jadi MODIFY COLUMN di tiga kolom itu bukan kosmetik: dia bener-bener ganti daftar nilai yang valid di level database. users.bidang tetap NOT NULL dengan default ALL, dua kolom lainnya nullable buat konten global. Tiap MODIFY disusul UPDATE ... LOWER() buat baris lama.
Lucunya, ngecek data dulu ternyata penting. Data bidang yang ada baru sebanyak nilai default dari migrasi 000057, jadi UPDATE itu sebenernya no-op guard: disertain aja biar migrasi tetep bener kalau suatu saat datanya udah berkembang. Satu nilai juga sengaja nggak ikut dikecilin: ALL. Itu bukan nama bidang, melainkan sentinel buat superadmin. Kalau dia ikut lowercase, dia jadi nilai tak dikenal dan berakhir di deny-all yang sama. Pengecualian ini justru nunjukin aturan mainnya: sistem strict sama string literal, dan sentinel ya harus kelihatan beda.
Ujung-ujungnya frontend, dan yang sengaja nggak disentuh
Di frontend, key map label formatBidang ikut pindah dari UPPERCASE ke lowercase di commit yang sama, bareng tipe hasil generate dari spec dan daftar opsi di form. Biar rantainya nggak putus: yang keluar dari API lowercase, yang disimpen map lowercase, yang diputuskan resolveBidang juga lowercase kecuali ALL. Satu aja yang kelewat, UI balik nampilin nilai mentah.
categories.module sengaja nggak disentuh. Nilainya mirip dan juga enum, tapi itu domain modul kategori, bukan scope bidang. Migrasi yang disiplin itu jelas batasnya, termasuk batas apa yang nggak boleh ikut kegas.
File down-nya jadi asuransi: balikin definisi kolom ke UPPERCASE dan UPPER()-kan datanya. Satu hal yang saya bawa pulang dari ini: permintaan sekecil apa pun yang nyentuh nilai enum, saya perlakukan sebagai perubahan kontrak sejak detik pertama, bukan kosmetik yang ditunda sampai malam.
Jejak kehati-hatian ini kelihatan juga di test. TestResolveBidang yang lama menyeragamkan input form pakai strings.ToUpper; versi barunya justru nge-lowercase semua input kecuali ALL, dan matriks kasusnya dipinjam lengkap biar perubahan arah normalisasi nggak bisa lewat diam-diam. Klien lama yang masih ngirim OLAHRAGA tetap diterima, tinggal dikecilkan sebelum disimpan. Yang jadi soal bukan input yang bocor casing, melainkan kesepakatan bentuk nilai yang tersimpan di satu sumber kebenaran.
openapi.yaml jadi tempat nempelnya catatan kontrak: enum bidang di spec sekarang bawa deskripsi lowercase sejak migrasi 000059 plus keterangan bahwa ALL itu sentinel. Karena tipe frontend digenerate dari spec yang sama, catatan sekali nulis kepake di dua sisi sekaligus. Di titik ini saya sadar spec itu bukan formalitas buat dipajang; dia dokumentasi yang ikut dikompilasi, dan pada perubahan kontrak kayak gini, dia yang paling murah diupdate paling akhir dilupain.