Geri Dön

Repository Design Pattern Nedir? ASP.NET Core ile Adım Adım Uygulama

Bu yazıda, yazılım projelerinde sık kullanılan Repository Design Pattern konusunu ASP.NET Core üzerinden hem teorik hem de pratik olarak ele alacağım

 Repository Design Pattern Nedir? ASP.NET Core ile Adım Adım Uygulama

Herkese merhaba. Bu yazıda, yazılım projelerinde sık kullanılan Repository Design Pattern konusunu ASP.NET Core üzerinden hem teorik hem de pratik olarak ele alacağım. Amacım, kendi öğrendiğim ve deneyimlediğim yaklaşımlarla repository yapısını adım adım kurup, veri erişim katmanını daha düzenli, sürdürülebilir ve SOLID’e uygun hale getirmenin yollarını göstermek

Repository Design Pattern Nedir?

Bildiğimiz gibi her model için bazı ortak işlemler vardır. Bu işlemlere kısaca CRUD işlemler diyebiliriz. Düşünelim ki projemizde Order adında bir tane modelimiz var.

Bu model için de temek CRUD işlemlerini yazmak zorundayız:

Burada modellerimiz aslında farklı olsa da yaptığımız temel CRUD işlemleri aynıdır. Projemizde modellerimiz arttıkça, bu ortak metotlar her bir model için ayrı ayrı uygulanmak zorunda. Örneğin GetProductById ile GetOrderById metotları aynı veritabanı sorgularını çalıştırır ve aynı işlemi yaparlar değişen tek şey bu işlemi hangi model için yaptıklarıdır.

İşte bu kod tekrarına düşmemek adına, tüm modeller için ortak kullanılabilir bir yapı oluşturmak generic repository design patern bunun tanımıdır. Generic repository pattern, ortak işlemlerimiz için genel bir yapı kurup her bir modelin bu genel yapı üzerinden o işlemi gerçekleştirmesini sağlar. Böylece kod tekrarlarından kaçınırız. Aşağıdaki kısımda repository pattern in nasıl uygulanacağını gösteriyor olacağız.

Repository Pattern tek bir IRepository<T> ile uygulanabilir ya da IReadRepository<T> ve IWriteRepository<T> olarak ayrılabilir. Tek yapı doğrudan SOLID’e aykırı değildir; ancak Interface Segregation Principle (ISP), yani “client’lar kullanmadıkları metotlara bağımlı olmamalıdır” prensibi gereği, sadece okuma yapan bir servisin yazma operasyonlarını bilmesi doğru değildir. Bu yüzden özellikle büyüyen sistemlerde read/write ayrımı daha temiz ve modüler bir yapı sağlar.

Lafı çok uzatmadan pratiğe geçelim.

BaseModel Oluşturmakla Başlayalım

Uygulamamızdaki id gibi ortak olacak property’lerini bir araya getirecek bir Base Model oluşturuyoruz

Ardından bu BaseEntity’den miras alacak modellerimizi oluşturuyoruz.

DbContext Oluşturmak

Modellerin ardından projemizde kullanacağımız DbContext’imizi oluşturmamız gerekiyor.

Öncelikle DbContext sınıfını kullanabilmek için “Microsoft.EntityFrameworkCore” paketini projemize dahil etmemiz gerekiyor.

Program.cs tarafında ConfigureServices metodunda DbContext’imizi servis konteynerine ekliyoruz:

DatabaseConnection adlı ConnectionString’imizi appsettings.json’da tanımlıyoruz:

DbContext imiz de hazır. Şimdi migration işlemlerini yaparak veritabanımızı oluşturuyoruz:

PM > Add-Migration Initial

PM > Update-Database

Generic Repository Oluşturmaya geçelim

Base Repository sınıfı oluşturmakla başlayalım

Neden DbSet<T> Table {get;} şeklinde bir şey tanımladık?

Hatırlarsanız repository kullanmadığımız senaryoda genelde servis içerisinde örneğin Add metodu için şunu yapıyorduk:

ani servis doğrudan DbContext’e bağımlı. Buradaki yazdığımız DbSet<T> şu işe yarar: _context.Orders gibi her entity için ayrı property kullanmak yerine _context.Set<T>() ile generic erişim sağlamak. Yani aslında DbSet<T>: Generic reposiory içinde ortak CRUD yazabilmemizi sağlar.

IReadRepository

Bu sınıfın amacı sadece listeleme ve okuma işlemleri içindir.

IQueryable yazmamızın nedeni şudur: biz eğer sorgu üzerinde çalışacaksak IQueryable yazmamız gerekiyor eğer burada List yazarsak bu IEnumerable dir ve bu işlemleri InMemory’de yapar. yani diyelim ki veritabanımda 1 milyon kayıt var. Ben eğer List dersem bu 1 milyon kaydı RAM’e yükler ve buradan sonra filtreleme ya da listeleme yapar ama IQueryable dersem bu veritabanından sadece gerekli kısımları getirir.

IWriteRepository

Bu sınıfın amacı ekleme silme güncelleme sınıfları içindir

IOrderReadRepository

Şimdi diyeceksiniz ki zaten bir IReadRepository oluşturduk. Neden bir daha IOrderRead oluşturuyoruz?

Biz burada IOrderReadRepository isminde ekstra bir interface tanımlıyoruz çünkü ortak listeleme ve temel okuma işlemlerinin imzalarını IReadRepository<T> içerisinde tanımlayıp, Order’a özel işlemleri ise ayrı bir yerde tutmak istiyoruz. IOrderReadRepository, IReadRepository<Order> arayüzünden kalıtım alarak hem genel okuma sözleşmesini kullanır hem de sadece Order’a özgü metotları eklememize imkân tanır. Böylece hem ortak yapıyı tekrar etmemiş oluruz hem de ileride Order’a özel bir sorgu yazmamız gerektiğinde doğru yerde konumlandırmış oluruz.

IOrderReadRepository

Aynı işlem bu sınıf için de geçerli.

Oluşturduğumuz bu interface lerin şimdi concrete lerini oluşturalım.

ReadRepository

ReadRepository tamamlandı. Şimdi ise WriteRepository’ye bakalım

WriteRepository

Read ve Write repository’lerimizi generic olarak tanımladıktan sonra, şimdi bu yapıyı entity bazında nasıl genişletebileceğimize bakalım.

OrderReadRepository

Ben burada OrderReadRepository sınıfını oluşturmamın sebebi, ReadRepository<T> içerisinde tanımladığım ortak okuma işlemlerini tekrar yazmamak ve ileride sadece Order’a özel bir metot eklemek istediğimde doğru bir genişletme noktası oluşturmaktır. ReadRepository<T> tüm entity’ler için ortak olan temel listeleme ve getirme işlemlerini içerirken, OrderReadRepository bu yapıyı miras alarak hem hazır gelen davranışları kullanır hem de sadece Order modeline özgü sorguları ekleyebileceğim bir alan sağlar. Böylece hem kod tekrarının önüne geçmiş olurum hem de generic yapı ile entity’ye özel davranışı birbirinden ayırmış olurum. Eğer ileride Order’a özel bir filtreleme, join ya da include işlemi yazmam gerekirse bunu doğrudan OrderReadRepository içerisinde tanımlayabilirim.

OrderWriteRepository Sınıfının oluşturulması

Yukarıdaki OrderReadRepository için geçerli olan her şey burada da geçerli. Ben OrderWriteRepository sınıfı için de özel bir metot eklemek istediğimde bu sınıfta ekleyebilirim. Şuanda bu sınıf WriteRepository den miras aldığı için bütün WriteRepository’deki işlemlerimi yapabilecek.

Repository sınıflarımızı tanımladıktan sonra, bunları uygulama içerisinde kullanabilmemiz için dependency injection container’a eklememiz gerekir. Özellikle controller katmanında bu repository’leri kullanabilmek adına, Program.cs dosyasında ilgili arayüz ve implementasyon eşleşmelerini tanımlamamız gerekir. Bu sayede ASP.NET Core, ihtiyaç duyulduğunda ilgili repository instance’ını otomatik olarak oluşturup controller’a enjekte edebilir.

Controller Katmanında Repository Kullanımı

Daha önce de dediğimiz gibi Order’a özel işlem yapmak için OrderReadRepository oluşturmuştuk. Şimdi gelin Order’a özel metotu controller da nasıl kullanacağımıza bakalım

Şimdi ise ReadRepository üzerinden Order için nasıl işlem gerçekleştirebileceğimizi inceleyelim.

Gördüğümüz gibi repository yapısını generic bir temel üzerine kurup, ihtiyaç duyduğumuz entity’lerde genişleterek hem kod tekrarını engelleyebilir hem de daha sürdürülebilir bir mimari oluşturabiliriz. Önemli olan fazla soyutlama üretmek değil, ihtiyaca göre doğru seviyede genişletebilmektir. Doğru kurgulanmış bir repository yapısı, özellikle büyüyen projelerde hem okunabilirliği artırır hem de bağımlılıkları daha kontrollü yönetmemizi sağlar.