Soft Delete: Kapan Berguna, Kapan Jadi Beban
Trait SoftDeletes di Eloquent itu salah satu fitur yang paling gampang dipakai tanpa dipikir dua kali -- tinggal tambah satu baris use SoftDeletes; dan kolom deleted_at, lalu semua delete() jadi "aman" karena data sebenarnya tidak hilang. Masalahnya, kemudahan ini sering bikin orang memasangnya di semua model secara default, padahal soft delete punya konsekuensi nyata di level query, index, dan constraint yang baru kelihatan setelah aplikasi jalan lama. Menurut saya ini keputusan desain yang layak dipikirkan per tabel, bukan kebiasaan yang diterapkan begitu saja.
Kapan soft delete benar-benar berguna
Soft delete paling masuk akal untuk data yang punya nilai historis atau legal walau sudah tidak aktif -- catatan transaksi, riwayat kunjungan, invoice, atau apa pun yang mungkin perlu diaudit atau dipulihkan di kemudian hari. Di sistem yang berhubungan dengan data klinis misalnya, menghapus baris secara permanen bisa berarti kehilangan jejak yang sebenarnya wajib disimpan untuk kebutuhan audit atau rekonsiliasi. Untuk kasus seperti ini, soft delete bukan sekadar kenyamanan teknis, tapi kebutuhan bisnis: user "menghapus" record dari tampilannya, tapi data tetap ada untuk keperluan lain yang tidak selalu terlihat dari sisi UI.
Nilai lain yang sering diremehkan adalah kemudahan pulih dari kesalahan. Kalau ada staf yang salah pencet hapus data penting, soft delete memberi jendela waktu untuk restore tanpa harus buka backup. Ini murah untuk diimplementasikan dan mahal manfaatnya justru di momen darurat -- jenis fitur yang jarang dipuji tapi selalu disyukuri saat dibutuhkan.
Kapan soft delete jadi beban
Di sisi lain, untuk data yang memang boleh benar-benar hilang -- draft sementara, cache, log rutin, atau tabel pivot yang tinggi frekuensi tulisnya -- soft delete cuma menambah beban tanpa manfaat sepadan. Setiap query jadi otomatis menambahkan klausa WHERE deleted_at IS NULL, tabel terus bertambah besar karena baris "terhapus" tidak pernah benar-benar pergi, dan index harus dirancang lebih hati-hati supaya query tetap cepat walau tabel makin gemuk. Untuk tabel dengan volume tulis tinggi, saya lebih memilih hard delete plus, kalau memang perlu jejak, tabel log terpisah yang lebih ringan dan tidak ikut campur di jalur query utama.
Ada juga biaya operasional yang sering terlewat: setiap developer baru yang join tim harus tahu tabel mana yang pakai soft delete, karena query manual lewat DB client atau job maintenance yang lupa memperhitungkan deleted_at bisa menghasilkan laporan yang salah tanpa error apa pun -- data terhapus ikut kehitung, atau sebaliknya, data aktif malah terlewat.
Jebakan unique constraint
Ini bagian yang paling sering menjebak developer yang baru pertama kali pakai soft delete serius: unique constraint di database tidak tahu apa-apa soal deleted_at. Kalau kolom email punya unique index biasa, lalu user dengan email tertentu dihapus (soft delete) dan mencoba daftar ulang dengan email yang sama, insert itu akan gagal -- karena secara fisik baris lama masih ada di tabel, cuma disembunyikan oleh scope Eloquent.
Schema::table('users', function (Blueprint $table) {
$table->dropUnique(['email']);
$table->unique(['email', 'deleted_at']);
});Solusi umum seperti di atas -- menjadikan constraint komposit dengan deleted_at -- membantu, tapi punya batasannya sendiri: kalau ada dua baris yang sama-sama soft deleted dengan deleted_at yang berbeda, keduanya tetap dianggap unik, jadi tidak sepenuhnya mencegah duplikasi data "mati". Sebagai ilustrasi, kalau kebutuhannya justru mencegah dua baris aktif punya nilai sama, sebagian tim memilih pendekatan lain: kolom penanda unik yang di-null-kan saat soft delete, atau constraint yang dicek di level aplikasi lewat validasi sebelum insert, bukan murni mengandalkan database. Intinya, begitu sebuah kolom butuh unique constraint dan tabelnya pakai soft delete, aturan constraint itu harus didesain ulang secara sadar, bukan dianggap otomatis ikut aman.
Takeaway
Soft delete bukan default yang aman untuk semua tabel -- ia cocok untuk data yang punya nilai audit atau butuh jalan pulih dari kesalahan, dan jadi beban untuk data yang volumenya tinggi atau memang boleh hilang permanen. Dan begitu sebuah tabel soft delete punya unique constraint, jangan asumsikan itu langsung berfungsi seperti yang dibayangkan -- cek ulang perilakunya sebelum dipakai di produksi.