Repository Deseni: Alan Kodunu Veri Erişiminden Ayırmak
Repository arayüzünü alan diliyle tasarlayın, bellek ve veritabanı gerçeklemelerini yazın, Unit of Work ve generic repository tuzaklarını görün.
Repository deseni neyi çözer?
Bir iş uygulamasında alan kodu (sipariş verilebilir mi, indirim uygulanır mı) ile veri erişimi kodu (SQL, ORM çağrıları, sayfalama) çoğu zaman iç içe geçer. Servis sınıfı hem iş kuralını yürütür hem de sorgu yazar. Sonuç: iş kuralını test etmek için veritabanı gerekir, veri kaynağını değiştirmek iş kodunu bozar ve aynı sorgu birkaç yerde kopyalanır.
Repository deseni, veri erişimini koleksiyon benzeri bir arayüz arkasına saklar. Martin Fowler'ın kurumsal uygulama örüntüleri kataloğu bunu alan ile veri eşleme katmanları arasında aracılık eden, alan nesnelerine koleksiyon gibi erişim sağlayan bir arayüz olarak tanımlar. Alan kodu "şu kimlikteki siparişi getir" ya da "siparişi kaydet" der; bunun SQL ile mi, belge veritabanıyla mı, bellekteki bir listeyle mi yapıldığını bilmez.
Kazanç şudur: alan kodu saflaşır ve veri erişimi tek yerde toplanır. Bedeli ek bir katmandır; çok basit CRUD uygulamalarında bu katman değer katmadan tören yaratabilir.
Adım adım: arayüzü alan dilinde tasarlamak
Birinci adım, repository arayüzünü veri teknolojisine değil, alanın diline göre yazmaktır. Metot adları iş kavramlarını yansıtmalıdır:
interface SiparisDeposu { kimligeGoreBul(kimlik: string): Siparis | null; customerOpenOrders(customerId: string): Siparis[]; kaydet(siparis: Siparis): void; }
Arayüz alan katmanında durur. Gerçekleştirmeler ise altyapı katmanındadır; bu bağımlılık yönü, temiz mimarideki "bağımlılıklar içe doğru akar" fikrinin doğrudan uygulamasıdır.
İkinci adım, veritabanı gerçeklemesini yazmaktır. Burada sorgu, eşleme ve dönüştürme ayrıntıları kalır:
class SqlSiparisDeposu implements SiparisDeposu { kimligeGoreBul(kimlik: string) { const satir = this.baglanti.tekSatir("select * from siparis where kimlik = ?", [kimlik]); return satir ? SiparisEslemesi.alana(satir) : null; } /* diğer metotlar */ }
Üçüncü adım, bellekte çalışan bir sahte gerçekleme yazmaktır:
class BellekSiparisDeposu implements SiparisDeposu { private liste = new Map<string, Siparis>(); kimligeGoreBul(kimlik: string) { return this.liste.get(kimlik) ?? null; } kaydet(siparis: Siparis) { this.liste.set(siparis.kimlik, siparis); } /* diğer metotlar */ }
Dördüncü adım, servisi arayüze bağlamak ve gerçeklemeyi dışarıdan vermektir. Üretimde SQL, testte bellek gerçeklemesi kullanılır; iş kuralı testleri milisaniyeler içinde, veritabanı olmadan koşar.
Unit of Work: değişiklikleri tek işlemde toplamak
Bir iş akışı genellikle birden fazla nesneyi değiştirir: siparişi kaydeder, stoğu düşürür, müşteri puanını günceller. Her repository kendi kaydını ayrı işleyip hata anında eksik kalırsa veri tutarsız olur. Unit of Work (iş birimi), bir iş işlemi boyunca etkilenen nesneleri izleyen ve değişiklikleri tek seferde, tek işlem içinde yazan yapıdır. Fowler'ın kataloğunda, etkilenen nesnelerin listesini tutan ve değişikliklerin yazılmasını ve eşzamanlılık sorunlarının çözümünü koordine eden örüntü olarak anlatılır.
Uygulamada akış şöyledir: iş birimi başlatılır, repository'ler aynı iş birimini paylaşarak nesneleri okur ve değiştirir, sonunda tek bir tamamlama (commit) çağrısı yapılır. Hata olursa tamamının geri alınması kolaylaşır.
Birçok ORM, değişiklik izleme ve oturum nesnesiyle iş birimini zaten sağlar. Bu durumda kendi iş birimi soyutlamanızı yazmak gerekip gerekmediğini düşünün: gerek, ORM'in tiplerini alan katmanından uzak tutmak istiyorsanız vardır; yoksa gereksiz bir ikinci katman olur.
Genel (generic) repository tuzağı ve sorgu sızıntısı
Her varlık için aynı temel metotları (bul, hepsini getir, ekle, sil, güncelle) sunan genel bir repository cazip görünür. Ancak alan dilini kaybettirir: "müşterinin açık siparişleri" gibi iş sorusu, çağıran kodda filtre ifadeleriyle yeniden kurulur ve sorgu mantığı servislerde dağılır.
Daha sağlıklı yaklaşım, repository'yi toplulaştırma kökü (aggregate root) başına yazmak ve iş sorularını adlandırılmış metotlar olarak sunmaktır. Bir siparişin satırları ayrı repository'ye sahip olmaz; sipariş repository'si siparişi satırlarıyla birlikte getirir ve kaydeder.
Sorgu sızıntısı da yaygın bir hatadır. Repository, ORM'in sorgulanabilir (genellikle ertelenmiş) nesnesini döndürürse çağıran kod sorguyu kendi başına şekillendirir ve veri erişimi sınırı delinir. Somut liste döndürmek ya da sayfalama gibi ihtiyaçları parametre nesneleriyle açıkça ifade etmek bu sızıntıyı önler.
Salt okunur, listeleme ve rapor ağırlıklı ekranlar için ayrı bir okuma modeli düşünebilirsiniz: yazma tarafında repository, okuma tarafında doğrudan optimize edilmiş sorgu. Bu ayrım, repository'yi ekran sorgularının yükünden kurtarır.
Test stratejisi: ne neyle sınanır?
Repository desenine geçtiğinizde test çeşitleri de ayrışır. Alan ve uygulama servisi testleri, bellek gerçeklemesiyle hızlı birim testlerine dönüşür. Veritabanı gerçeklemesi ise gerçek bir veritabanıyla entegrasyon testlerinde sınanır; böylece eşleme hataları, dönüştürme ve sorgu doğruluğu yakalanır.
Gerçeklemelerin aynı davranışı gösterdiğinden emin olmak için ortak bir sözleşme testi yazılabilir: aynı test paketi hem bellek hem de veritabanı gerçeklemesine uygulanır. Bellek sürümünün yanlış davranışı, testlerin yanlış güven vermesine yol açabileceği için bu paket önemli bir güvencedir.
Dikkat edilecek nokta, bellek gerçeklemesinin veritabanının tüm özelliklerini (işlem yalıtımı, kilitler, sıralama farkları) taklit edemeyeceğidir. Bu yüzden iş açısından kritik akışlarda gerçek veritabanı üzerinde en az bir uçtan uca entegrasyon testi bulundurmak gerekir.
Sık yapılan hatalar
- Repository'yi servis gibi kullanmak: İçine iş kuralı koymak, veri erişimi ile alan mantığını yeniden karıştırır.
- Her tablo için bir repository yazmak: Repository tabloyu değil, toplulaştırma kökünü temsil eder.
- Kaydet metodunda gizli yan etkiler: Kaydet çağrısı e-posta göndermek ya da başka sistemleri tetiklemek gibi sürprizler içermemelidir.
- Üst katmanlara ORM varlıklarını sızdırmak: Alan nesneleri ile veritabanı varlıkları aynı tipse bile arayüzde veritabanına özgü ayrıntıları göstermeyin.
- İşlem sınırını belirlememek: Her repository çağrısı kendi işlemini açarsa iş akışı boyunca tutarlılık sağlanamaz; sınır iş birimi ya da uygulama servisi düzeyinde olmalıdır.
- Önbelleği repository içine gizlice gömmek: Önbellek gerekiyorsa bunu dekoratör olarak sarın; böylece davranış açık kalır ve ayrı sınanır.
Bu beceriyi Mindset programında nerede çalışırsınız?
Yazılım Mimarisi ve Tasarım Desenleri programı, SOLID ve tasarım prensipleri ile mimari stiller modüllerinde katmanlama ve bağımlılık yönü kararlarını ele alır; repository gibi veri erişim sınırı desenleri bu çerçevede anlam kazanır. Program, katmanlı ve temiz mimari yaklaşımlarla çalışan geliştiriciler ve veri erişim katmanını sadeleştirmek isteyen teknik liderler için uygundur.
Kurumsal ekiplerde en somut kazanç, iş kuralı testlerinin veritabanına bağımlı olmaktan çıkması ve veri teknolojisi değişimlerinin (ORM yükseltmesi, farklı bir veri deposuna geçiş) iş koduna dokunmadan yönetilebilmesidir.
İlgili Mindset programı
Yazılım Mimarisi ve Tasarım Desenleri
Sık sorulan sorular
Repository deseni ile ORM aynı şey mi?
Hayır. ORM, nesne ile ilişkisel tablo arasında eşleme yapan bir araçtır; Repository ise alan katmanının veri erişimini tanımladığı bir arayüzdür. Repository'nin gerçeklemesi ORM kullanabilir. ORM bazı kapsamlarda koleksiyon benzeri davranış sunsa da, onu alan arayüzünün arkasına saklamak veri teknolojisinden bağımsızlık ve test kolaylığı sağlar.
Her varlık için ayrı repository yazmalı mıyım?
Hayır. Repository, tabloyu değil toplulaştırma kökünü temsil eder. Sipariş ve sipariş satırları birlikte tek bir bütünse, tek bir sipariş repository'si ikisini birlikte getirir ve kaydeder. Her tablo için repository yazmak, alan modelini veritabanı şemasına bağlar ve iş sorularını çağıran kodda dağıtır.
Genel (generic) repository kullanmak iyi bir fikir mi?
Tekrarlı kodu azaltsa da genellikle alan dilini kaybettirir: iş soruları çağıran tarafta filtre ifadeleriyle kurulur ve sorgu mantığı dağılır. Ortak altyapı için genel bir taban kullanılabilir, ama dışarıya açılan arayüz alanın adlandırılmış sorularını içermelidir, örneğin müşterinin açık siparişleri.
Unit of Work ne zaman gerekir?
Bir iş akışı birden çok nesneyi değiştirip hepsinin birlikte kaydedilmesi ya da hiçbirinin kaydedilmemesi gerekiyorsa gerekir. Birçok ORM bu davranışı oturum ya da bağlam nesnesiyle zaten sağlar. O durumda ayrı bir soyutlama yalnızca ORM tiplerini alan katmanından uzak tutmak istediğinizde anlamlıdır.
Repository testini nasıl yazarım?
Servis testlerinde bellek tabanlı bir sahte gerçekleme kullanın, böylece testler hızlı ve veritabanından bağımsız olur. Veritabanı gerçeklemesini gerçek bir veritabanıyla entegrasyon testinde sınayın. Mümkünse aynı sözleşme testini tüm gerçeklemelere uygulayın ki bellek sürümü gerçek davranıştan sapmasın.
İlgili rehberler
Kategori: Yazılım Geliştirme