Ghost Kayıt Çakışması
Kurumsal B2B SaaS platformları geliştirirken, veri tabanından kayıtları sert bir şekilde silmek operasyonel bir yük haline gelebilir. Eğer bir müşteri yanlışlıkla bir iş alanını silerse, onu anında geri yükleme yeteneğine sahip olmalısınız. Endüstri standardı çözüm, Laravel’in SoftDeletes özelliğidir; bu özellik, satırın veri tabanından fiziksel olarak kaldırılmak yerine, deleted_at zaman damgası eklemesini sağlar.
Ancak, Soft Deletes’i kullanmak, Benzersiz Veri Tabanı Kısıtlamaları ile büyük, gizli bir mimari çakışma oluşturur. Örneğin, users isimli bir tabloya sahip olduğunuzu varsayın; burada email sütununun benzersiz olması gerekmektedir. Bir kullanıcı [email protected] ile kaydoldu. Bir hafta sonra, hesabını sildi (soft delete). Bir ay sonra tekrar [email protected] ile kaydolmaya çalışıyor.
Laravel doğrulamanız geçebilir (eğer soft-deleted satırları göz ardı edecek şekilde yapılandırıldıysa), ancak Eloquent kaydı eklemeye çalıştığında, PostgreSQL/MySQL veri tabanı Integrity constraint violation: 1062 Duplicate entry hatasını verir. Veri tabanı, PHP deleted_at sütununuzu umursamaz; yalnızca iki aynı e-postayı görür. Sağlam mimariler oluşturmak için fiziksel veri tabanı şemanızın soft-delete mantığınızı anlaması gerekir.
Çözüm: Bileşik Benzersiz İndeksler
Bunu düzeltmek için, email sütununda basit bir benzersiz indeks üzerine güvenemeyiz. Bileşik Benzersiz İndeks oluşturmalıyız; bu, email sütununu deleted_at sütunu ile birleştirir. Ancak dikkat edilmesi gereken bir nokta vardır: standart SQL’de birden fazla NULL değeri eşit kabul edilmez. Eğer deleted_at NULL ise, benzersiz kısıtlama farklı SQL diyalektlerinde beklenmedik bir şekilde davranır.
PostgreSQL ve MySQL İçin Şema Mimarisi
Modern veri tabanı motorlarında bunu en güvenilir şekilde yönetmenin yolu, Kısmi Benzersiz İndeks (PostgreSQL için yerel) veya oluşturulmuş sanal sütunlar (MySQL) kullanmaktır.
Laravel’in Blueprint’i kullanarak PostgreSQL için temiz, kurumsal düzeyde bir migration örneği:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('email');
$table->string('name');
$table->timestamps();
$table->softDeletes(); // 'deleted_at' sütununu ekler
// ❌ ANTI-PATTERN: Bu, soft-deleted bir kullanıcı tekrar kaydolduğunda çökmesine neden olacaktır
// $table->unique('email');
});
// ✅ KURUMSAL PATERNI (PostgreSQL):
// Aktif, silinmemiş satırlar için yalnızca e-posta benzersizliğini sağlamaktadır!
DB::statement('
CREATE UNIQUE INDEX users_email_active_unique
ON users (email)
WHERE deleted_at IS NULL;
');
}
public function down(): void
{
Schema::dropIfExists('users');
}
}
FormRequest Doğrulamasını Güncelleme
Artık veri tabanınız güvenli, ancak Laravel’in HTTP doğrulama katmanına, soft-delete sınırını göz önünde bulundurmaları için talimat vermelisiniz; böylece kullanıcılar 500 Sunucu Hatası yerine temiz JSON hata mesajları alabilir.
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StoreUserRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => [
'required',
'email',
// Laravel'e yalnızca deleted_at NULL olan satırlar arasında benzersizliği kontrol etmesini söyleyin
Rule::unique('users')->whereNull('deleted_at'),
],
'name' => ['required', 'string'],
];
}
}
Mühendislik ROI’si
Benzersiz kısıtlama mantığınızı kısmi veri tabanı indekslerine kaydırarak, mutlak veri bütünlüğü elde edersiniz. Ölümcül 500 SQL hatalarını ortadan kaldırır, kullanıcıların hesaplarını sildikten sonra sorunsuz bir şekilde yeniden kaydolmalarına olanak tanır ve tarihsel denetim izlerinizi mükemmel bir şekilde korursunuz; veri tabanının benzersizlik garantilerini tehlikeye atmadan.
Kaynak: Orijinal Makale


