Repository pattern implementation, data access layer abstraction ve domain-infrastructure separation. CQRS pattern ile entegrasyon.
DDD
Repository
Pattern
Data Access
Abstraction
Artık Order gibi Aggregate’leri ve onun sınırları içindeki Entity ve Value Object’leri nasıl modelleyeceğimizi öğrendik. Peki, bu Aggregate’leri nasıl oluşturacak, güncelleyecek ve kalıcı hale getireceğiz?
İşte burada Domain-Driven Design’ın bir diğer yapı taşı devreye giriyor: Repository.
Repository, Aggregate’lerimizi kalıcı depolama (ör. veritabanı) ile etkileşime sokan ve bunu domain kodundan soyutlayan katmandır. Şimdi Repository’nin ne olduğunu ve neden önemli olduğunu inceleyelim.
📦 Repository Nedir?
DDD’de Repository, Aggregate Root’ların dış dünyayla (örneğin veritabanı) olan tüm veri erişim işlemlerini kapsülleyen bir yapıdır.
Amaç, domain katmanını veritabanı detaylarından tamamen bağımsız hale getirmektir.
🎯 Repository’nin Amacı
Amaç
Açıklama
Persistence'ı soyutlamak
Domain katmanı, EF Core, Mongo, SQL gibi veri katmanından haberdar olmaz
Aggregate Root’a tek kapı sunmak
Aggregate’leri hep Root seviyesinden yükleyip kaydederiz
Kolay test ve bakım
Veritabanı olmadan domain kodunu test edebiliriz
🧠 Önemli:
👉 Repository Aggregate Root’lar için tasarlanır.
👉 Alt Entity veya Value Object’ler için ayrı repository yazılmaz.
📝 OrderRepository Arayüzü
Örneğin Order Aggregate için bir Repository şöyle başlar:
public class OrderRepository : IOrderRepository
{ private readonly AppDbContext _context; public OrderRepository(AppDbContext context){ _context = context;} public async Task<Order> GetByIdAsync(Guid id){return await _context.Orders
.Include(o => o.Items) .FirstOrDefaultAsync(o => o.Id ==id);} public async Task AddAsync(Order order){ await _context.Orders.AddAsync(order);} public async Task UpdateAsync(Order order){ _context.Orders.Update(order);} public async Task DeleteAsync(Order order){ _context.Orders.Remove(order);}}
Not: SaveChanges burada Repository içinde değil, Unit of Work ya da servis katmanı gibi üst katmanlarda çağrılır.
💡 Repository Kullanımının Faydaları
✅ Domain kodu persistence teknolojisinden bağımsız olur.
✅ Bir gün EF Core yerine başka bir ORM’e geçsen bile domain kodunu değiştirmek zorunda kalmazsın.
✅ Test yazmak kolaylaşır (örneğin: fake repository ile unit test).
✅ Aggregate tutarlılığı korunur: sadece Root’lar repository üzerinden işlenir.
🔑 Özet
Repository, Aggregate’leri dış dünyaya açan bir köprüdür ama domain kurallarını bozmadan, Aggregate sınırlarını aşmadan çalışır.
OrderRepository, UserRepository, ProductRepository gibi repository'ler hep Aggregate Root'lar için yazılır.
🧭 Repository’den Repository Pattern’e Geçiş
Artık Repository’nin DDD içindeki yerini ve görevini temel hatlarıyla kavradık.
Ancak dikkat edilmesi gereken bir konu var:
DDD’de kullandığımız Repository kavramı, klasik yazılım mühendisliğinde sıkça duyduğumuz Repository Design Pattern ile her zaman bire bir aynı değildir.
Bu iki kavramın arasındaki farkları anlamak, DDD ile doğru uyumlu bir yapı kurabilmek için oldukça önemlidir.
💡 DDD Repository ile Repository Design Pattern Arasındaki Farklar
| Özellik | DDD Repository | Klasik Repository Pattern |
| ---- | ---- |
| Amaç | Aggregate Root'ların persistence işlemlerini kapsüller ve domain kurallarını korur | Veri erişim katmanını soyutlayıp veri kaynaklarıyla doğrudan ilişkiyi keser |
| Nesne Seviyesi | Sadece Aggregate Root için yazılır | Genellikle her Entity için ayrı repository tanımlanır |
| Sınır (Boundary) | Aggregate sınırlarını korur, dış dünyaya Aggregate Root’tan başka bir şey açmaz | Çoğu zaman bu sınır dikkate alınmaz, her Entity'nin repo'su olabilir |
| Domain ile Bağlantı | Domain kurallarına hizmet eder, domain mantığının bir parçasıdır | Sadece teknik bir soyutlama katmanı görevi görür |
| Odak | Tutarlılık ve domain bütünlüğü | Veritabanı işlemlerini soyutlama (CRUD odaklıdır) |
| Kullanım Yeri | DDD'de domain odaklı tasarımın bir parçası | Her tür yazılım mimarisinde veri erişimi soyutlamak için kullanılabilir |
Hatırlarsanız yukarıda "SaveChanges burada Repository içinde değil, Unit of Work ya da servis katmanı gibi üst katmanlarda çağrılır." diye bir not belirtmiştik. Şimdi bu konuyu inceleyelim
🧭 Neden Unit of Work’e İhtiyacımız Var?
DDD'de Repository’ler, Aggregate Root'ların veriyle olan ilişkisini yönetir.
Ancak birden fazla Repository kullanıldığında veya birden fazla işlem bir bütün olarak yapılmak istendiğinde, tüm bu işlemlerin tek bir “iş birliği” içinde, tutarlı şekilde kaydedilmesi gerekir.
İşte bu noktada devreye Unit of Work girer.
Unit of Work Nedir?
Unit of Work, bir işlem (iş birliği) süresince yapılan tüm veri değişikliklerini bir arada tutar ve en sonunda tek bir SaveChanges çağrısıyla kalıcı hale getirir.
DDD'de şöyle düşün:
“Bir Use Case (örnek: Sipariş Oluşturma) = Bir Transaction = Bir Unit of Work”
Unit of Work Ne İşe Yarar?
Yarar
Açıklama
Tüm işlemleri tek noktadan commit etme
1 kez SaveChanges çağırırsın
Transaction bütünlüğü sağlar
Tüm işlemler ya birlikte olur, ya hiçbiri olmaz
Test edilebilirlik
Tek noktadan sahte (fake/mock) kontrol yapılabilir
Repository’leri koordine eder
Birden fazla Aggregate veya Repository ile çalışıldığında işlem sırası, save süreci kontrol altındadır
🛠️ Örnek: IUnitOfWork Arayüzü
bash
public interface IUnitOfWork
{ Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);}
⚙️ EF Core ile Uygulama
bash
public class EfUnitOfWork : IUnitOfWork
{ private readonly AppDbContext _context; public EfUnitOfWork(AppDbContext context){ _context = context;} public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default){return await _context.SaveChangesAsync(cancellationToken);}}
🧪 Kullanım Senaryosu (Servis Katmanı)
bash
public class OrderService
{ private readonly IOrderRepository _orderRepository; private readonly IUnitOfWork _unitOfWork; public OrderService(IOrderRepository orderRepository, IUnitOfWork unitOfWork){ _orderRepository = orderRepository; _unitOfWork = unitOfWork;} public async Task CreateOrderAsync(CreateOrderDto dto){ var order = new Order(...); order.AddItem(...); await _orderRepository.AddAsync(order); await _unitOfWork.SaveChangesAsync(); // Tüm değişiklikler burada commit edilir
}}
✅ Özet
Unit of Work, DDD'de iş akışının (Use Case) transactional bütünlüğünü sağlar.
Tüm Repository işlemlerini bir arada tutar ve domain’in consistency boundary’sine sadık kalır.