Repository Pattern Nedir? Neden Önemli?

Repository Pattern, iş katmanını (business logic) veri erişim detaylarından soyutlayan bir tasarım desenidir. Böylece:

  • Bağımlılık azalır: Servisleriniz doğrudan DbContext veya ADO.NET’e değil, bir arayüze (IRepository<T>) bağlı kalır.
  • Test edilebilirlik artar: Mock nesneleri aracılığıyla veri katmanını izole ederek birim testler yazabilirsiniz.
  • Kod tekrarı azalır: CRUD işlemlerini tek bir yerde toplar, projeler arası yeniden kullanım sağlar.

Çok katmanlı projelerde, karmaşık query’ler ya da farklı veri kaynakları gerektiğinde Repository Pattern, kodun sürdürülebilirliğini ve genişletilebilirliğini büyük ölçüde iyileştirir.

Temel Kavramlar

  • IRepository<T> arayüzü CRUD metotlarını tanımlar.
  • Somut sınıf (EfRepository<T>) bu arayüzü EF Core veya başka bir teknolojiyle uygular.
  • Unit of Work birden fazla repository işlemini tek bir transaction altında toplar.

Uyarı: Küçük, basit CRUD senaryolarında gereksiz katman eklemekten kaçının; doğrudan DbContext kullanmak daha pratiktir.

Avantajlar ve Dezavantajlar

Avantajlar

  1. Tek Sorumluluk (SRP): Veri erişim kodu, iş mantığından ayrılır.
  2. Test Edilebilirlik: Repository arayüzünü mock’layarak izole testler yazabilirsiniz.
  3. Genişletilebilirlik (OCP): Yeni bir veri kaynağı eklemek için sadece yeni bir sınıf implementasyonu yeterlidir.
  4. Tekrarlanabilirlik: Farklı projelerde aynı repository’yi rahatça kullanabilirsiniz.

Dezavantajlar

  1. Boilerplate Kod: CRUD metodlarının tekrarı bazı projelerde gereksiz yük oluşturabilir.
  2. Aşırı Soyutlama: Basit uygulamalarda karmaşıklığı artırabilir.
  3. Async Yönetimi: SaveChangesAsync() metodunu servis katmanında mı, yoksa repository içinde mi çağıracağınızı netleştirmek gerekiyor.

İpucu: SaveChangesAsync()’i servis katmanında tutmak, birden çok repository çağrısını tek transaction’da toplamanıza yardımcı olur.

ASP.NET Core Örnek Uygulama

1. Arayüz Tanımı

public interface IRepository<T> where T : class
{
    Task<T> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
    Task AddAsync(T entity);
    void Update(T entity);
    void Delete(T entity);
}

2. EF Core Somut Sınıf

public class EfRepository<T> : IRepository<T> where T : class
{
    protected readonly AppDbContext _context;

    public EfRepository(AppDbContext context)
    {
        _context = context;
    }

    public async Task<T> GetByIdAsync(int id)
    {
        return await _context.Set<T>().FindAsync(id);
    }

    public async Task<IEnumerable<T>> GetAllAsync()
    {
        return await _context.Set<T>().ToListAsync();
    }

    public async Task AddAsync(T entity)
    {
        await _context.Set<T>().AddAsync(entity);
    }

    public void Update(T entity)
    {
        _context.Set<T>().Update(entity);
    }

    public void Delete(T entity)
    {
        _context.Set<T>().Remove(entity);
    }
}

3. Unit of Work Entegrasyonu

public interface IUnitOfWork
{
    IRepository<Product> Products { get; }
    IRepository<Order> Orders { get; }
    Task<int> CommitAsync();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly AppDbContext _context;

    public UnitOfWork(AppDbContext context)
    {
        _context = context;
        Products = new EfRepository<Product>(context);
        Orders   = new EfRepository<Order>(context);
    }

    public IRepository<Product> Products { get; }
    public IRepository<Order> Orders   { get; }

    public async Task<int> CommitAsync()
    {
        return await _context.SaveChangesAsync();
    }
}

Not: UnitOfWork.CommitAsync() tüm değişiklikleri tek bir transaction altında commit eder; bu, veri tutarlılığı için önemlidir.

Servis Katmanına Entegrasyon

public class ProductService
{
    private readonly IRepository<Product> _repository;
    private readonly IUnitOfWork _unitOfWork;

    public ProductService(IRepository<Product> repository, IUnitOfWork unitOfWork)
    {
        _repository = repository;
        _unitOfWork = unitOfWork;
    }

    public async Task<Product> GetProductByIdAsync(int id)
    {
        return await _repository.GetByIdAsync(id);
    }

    public async Task AddProductAsync(Product product)
    {
        await _repository.AddAsync(product);
        await _unitOfWork.CommitAsync();
    }
}
  • Servis katmanı sadece iş kurallarını yönetir; veri kaydetme detayı tamamen IUnitOfWork aracılığıyla yapılır.
  • Hata durumlarında her iki katmanda da uygun loglama yaparak hatayı hızlıca tespit edebilirsiniz.

Gerçek Projelerde Dikkat Edilmesi Gerekenler

  • Yaşam Döngüsü (Lifetime): DbContext ve repository’lerinizi Scoped olarak yapılandırın; Singleton repository, DbContext sızıntılarına neden olur.
  • Performans İzleme: Sık sorgu yapan metotları Application Insights veya EF Core logging ile izleyin; N+1 sorgu problemlerine dikkat edin.
  • Metot Granülaritesi: Gereksiz büyük veri çeken GetAllAsync yerine, filtreli metotlar (GetByCategoryAsync) oluşturun.
  • Transaction Yönetimi: UnitOfWork veya TransactionScope kullanırken async/await uyumluluğunu göz önünde bulundurun.

İleri Okuma ve Kaynaklar

Sonuç

Repository Pattern, karmaşık ve büyüyen projelerde sürdürülebilirlik, test edilebilirlik ve kolay genişletilebilirlik sağlar. Küçük, basit CRUD uygulamalarında ise doğrudan DbContext kullanımı daha hızlı ve basit bir yaklaşımdır. Projenizin ölçeğine ve ihtiyaçlarına göre doğru dengeyi kurarak bu deseni verimli şekilde kullanabilirsiniz.

By tanju.bozok

Software Architect, Developer, and Entrepreneur

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir