Birçok modele bağlanan yorumlar için polymorphic ilişki mi yoksa ayrı tablolar mı; index ve foreign key açısından hangisi daha sağlıklı?
Soru
Bir Laravel uygulamasında yorumlar (comments) üç ayrı modele bağlanıyor: post, product ve ticket. Klasik yol olarak `morphMany` ile tek bir `comments` tablosu kullanmayı düşünüyorum ama gerçek foreign key kuramamak beni rahatsız ediyor. Alternatif olarak her tip için ayrı tablo (post_comments, product_comments, ticket_comments) açıp gerçek FK ve cascade delete kurabilirim. Index ve referential integrity açısından hangisi daha sağlıklı? Yeni tip eklemenin kolaylığı ile veritabanı bütünlüğü arasında nasıl karar vermeliyim?
Cevap
Kısa cevap: Polymorphic’in tek büyük bedeli gerçek foreign key kuramamaktır.
Kısa cevap
3 stabil tipiniz varsa ve bütünlük sizin için kritikse tek tabloda “exclusive arc” desenini (nullable tipli FK’ler + CHECK) tercih ederdim; saf morph’ta kalacaksanız morph map kullanmak şart.
Neden
-
Polymorphic’in gerçek bedeli: referential integrity yok.
commentable_idüç farklı tabloya işaret ettiği için veritabanı bir foreign key uygulayamaz. Bir post silindiğinde ona bağlı yorumlar orphan kalır; cascade delete ve tutarlılık tamamen uygulama koduna (observer, event, cron) kalır. Bu, DB’nin en güçlü garantisini elle taklit etmek demektir. -
Ayrı tablolar: temiz bütünlük, ama çoğalma. Gerçek FK, cascade, tipe özel constraint’ler — hepsi DB tarafından garantili. Bedeli: N tablo, şema ve kod tekrarı, migration yükü ve “bir kullanıcının tüm yorumları” gibi sorgular için UNION.
-
Orta yol — exclusive arc (tek tablo + tipli FK’ler). Tek
commentstablosu;post_id,product_id,ticket_idhepsi nullable, gerçek FK’li ve bir CHECK ile tam olarak biri dolu. Tek tablonun rahatlığı + gerçek foreign key. Bedeli: yeni tip eklemek yeni bir kolon, yani şema değişikliği.CREATE TABLE comments ( id BIGSERIAL PRIMARY KEY, body TEXT NOT NULL, post_id BIGINT REFERENCES posts(id) ON DELETE CASCADE, product_id BIGINT REFERENCES products(id) ON DELETE CASCADE, ticket_id BIGINT REFERENCES tickets(id) ON DELETE CASCADE, CONSTRAINT one_target CHECK ( (post_id IS NOT NULL)::int + (product_id IS NOT NULL)::int + (ticket_id IS NOT NULL)::int = 1 ) ); CREATE INDEX ON comments (post_id) WHERE post_id IS NOT NULL; CREATE INDEX ON comments (product_id) WHERE product_id IS NOT NULL; -
Index tarafı iki yaklaşımda da kolay. Morph’ta
(commentable_type, commentable_id)composite index (Laravel’in varsayılanı) “bu varlığın yorumları” sorgusunu iyi karşılar. Arc’ta her FK için partial index yeterlidir. Yani index, kararın belirleyicisi değil.
Ne yapmalı
-
Morph kullanacaksanız morph map olmadan yapmayın. Varsayılan olarak Laravel
commentable_type’a tam sınıf adını (App\Models\Post) yazar.enforceMorphMapile bunu kısa anahtara ('post') çevirin: index ve storage küçülür, en önemlisi sınıf adını yeniden adlandırdığınızda veritabanı bozulmaz. -
Karar sürücüsü: tip sayısı stabil mi, bütünlük ne kadar kritik. Tipler nadiren değişiyor ve integrity kritikse (ödeme, ticket gibi) arc veya ayrı tablolar. Tipler sürekli ekleniyor, esneklik integrity’den önemliyse morph + app-level bütünlük.
Sonuç: Ben olsam 3 stabil tip ve gerçek FK ihtiyacıyla exclusive arc’ı seçerdim — tek tablo kalır, cascade delete’i veritabanına bırakırım. Tiplerin sayısı belirsiz ve sürekli büyüyecekse saf morph’a geçer, enforceMorphMap ile anahtarlarımı sabitler ve orphan temizliğini bir observer ile kod tarafında garantiye alırdım. Kararı “hangisi şık” değil, “referential integrity’yi DB’ye mi bırakacağım” sorusuyla verirdim.
İlgili Yazılar
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.