Gereksinimler ve Ön Hazırlık
Uygulamaya başlamadan önce sisteminizde .NET 8 veya .NET 9 SDK'sının yüklü olduğundan emin olmalısınız. MediatR, modern .NET uygulamalarıyla tam uyumlu çalışır. Projenize kütüphaneyi eklemek için terminalinizi açın ve aşağıdaki komutu çalıştırın:
dotnet add package MediatR
Bu komut, MediatR'ın en güncel kararlı sürümünü projenize dahil edecektir. Ayrıca, bağımlılık enjeksiyonu (Dependency Injection) için Microsoft.Extensions.DependencyInjection paketinin projenizde yüklü olması gerektiğini unutmayın. Proje yapınızda "Features" veya "Application" adında bir klasör oluşturarak CQRS nesnelerinizi burada toplamanız, projenin modülerliğini artıracaktır.
Adım Adım MediatR Kurulumu ve Konfigürasyon
MediatR'ı kullanabilmek için öncelikle servis koleksiyonuna kayıt etmeniz gerekir. .NET uygulamalarında bu işlem genellikle Program.cs dosyasında gerçekleştirilir. MediatR, projenizdeki tüm handler (işleyici) sınıflarını otomatik olarak tarayabilir.
// Program.cs dosyası içerisinde
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
Bu yapılandırma, MediatR'ın projeniz içerisindeki tüm IRequestHandler arayüzlerini bulmasını ve DI konteynerine kaydetmesini sağlar. Bu sayede, yeni bir komut veya sorgu eklediğinizde manuel olarak servis kaydı yapmanıza gerek kalmaz.
Komut (Command) Yapısı Nasıl Oluşturulur?
Komutlar, sistemde bir durum değişikliği (create, update, delete) yapan işlemlerdir. Bir komut sınıfı, genellikle IRequest arayüzünü uygular. Aşağıdaki örnekte, bir kullanıcı oluşturma komutunu tanımlıyoruz.
public record CreateUserCommand(string Username, string Email) : IRequest;
Burada record kullanmamızın sebebi, verinin değişmez (immutable) olmasını sağlamaktır. IRequest ifadesi, bu komutun çalıştırıldığında geriye bir int (oluşturulan kullanıcının ID'si) döneceğini belirtir.
Komut İşleyici (CommandHandler) Geliştirme
Komut işleyicileri, komutun içerdiği mantığı yürüten sınıflardır. IRequestHandler arayüzünü uygularlar. Güvenlik açısından, burada gelen verilerin doğrulanması (validation) kritik öneme sahiptir.
public class CreateUserCommandHandler : IRequestHandler
{
private readonly MyDbContext _context;
public CreateUserCommandHandler(MyDbContext context) => _context = context;
public async Task Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
var user = new User { Username = request.Username, Email = request.Email };
_context.Users.Add(user);
await _context.SaveChangesAsync(cancellationToken);
return user.Id;
}
}
Kritik Güvenlik Uyarısı: Veritabanına doğrudan giriş yapmadan önce mutlaka FluentValidation gibi araçlarla girdi doğrulaması yapın. SQL Injection riskine karşı her zaman Entity Framework Core'un sağladığı parametreli sorguları kullanın ve asla ham SQL sorgularını kullanıcı girdisiyle birleştirmeyin.
Sorgu (Query) ve Sorgu İşleyici Mantığı
Sorgular, sistemdeki veriyi okumak için kullanılır ve hiçbir zaman veritabanında bir değişiklik yapmamalıdır. CQRS'in temel kuralı budur: Sorgular "Side-effect free" (yan etkisiz) olmalıdır.
public record GetUserByIdQuery(int Id) : IRequest;
public class GetUserByIdQueryHandler : IRequestHandler
{
private readonly MyDbContext _context;
public GetUserByIdQueryHandler(MyDbContext context) => _context = context;
public async Task Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
{
var user = await _context.Users.FindAsync(request.Id);
return new UserDto(user.Username, user.Email);
}
}
Bu örnekte, veritabanından gelen entity doğrudan döndürülmemiştir. Bunun yerine bir UserDto (Data Transfer Object) kullanılmıştır. Bu, veritabanı şemanız ile dış dünyaya sunduğunuz modelin ayrılmasını sağlar (Decoupling).
CQRS ve MediatR Karşılaştırması
| Özellik | Geleneksel Yöntem (Service Pattern) | MediatR ile CQRS |
|---|---|---|
| Kod Karmaşıklığı | Düşük (Başlangıçta) | Orta |
| Sorumluluk Ayrımı | Zayıf | Güçlü |
| Test Edilebilirlik | Zor | Çok Kolay |
| Ölçeklenebilirlik | Düşük | Yüksek |
Sıkça Sorulan Sorular
MediatR kullanmak performansı düşürür mü?
MediatR, süreç içi (in-process) bir kütüphanedir ve yansıma (reflection) kullanarak handler'ları bulur. Modern .NET sürümlerinde bu maliyet ihmal edilebilir düzeydedir. Performans kaybından ziyade, sağladığı temiz mimari avantajları çok daha değerlidir.
Bir komut içerisinde başka bir komutu çağırabilir miyim?
Teknik olarak mümkündür ancak CQRS prensiplerine aykırıdır. Komutlar ve sorgular birbirinden bağımsız olmalıdır. Eğer ortak bir mantık varsa, bunu bir "Service" veya "Domain Service" içerisine taşıyıp her iki handler'dan da çağırmalısınız.
Hata yönetimi (Exception Handling) nasıl yapılmalı?
MediatR'ın "Pipeline Behavior" özelliğini kullanarak merkezi bir hata yakalama mekanizması kurabilirsiniz. Bu sayede her handler içinde try-catch yazmak yerine, tüm hataları tek bir noktada loglayabilirsiniz.
CQRS her proje için uygun mu?
Hayır. Küçük CRUD (Create-Read-Update-Delete) uygulamaları için MediatR ve CQRS kullanmak "over-engineering" (gereksiz karmaşıklık) olabilir. Projenizin büyüklüğü ve iş mantığınızın karmaşıklığı arttıkça bu desenin faydaları ortaya çıkar.
Pipeline Behavior nedir?
Pipeline Behavior, MediatR'ın "Middleware" benzeri bir yapısıdır. Bir komut işlenmeden önce veya sonra (örneğin loglama, doğrulama, performans ölçümü) araya girmenizi sağlar.
MediatR ile Birim Testi (Unit Testing) Stratejileri
CQRS mimarisinde iş mantığını komut ve sorgu işleyicilerine hapsettiğinizde, test süreçleri oldukça öngörülebilir hale gelir. MediatR yapısını test ederken, Handler sınıflarınızı bağımsız birer birim olarak ele almalı ve dış bağımlılıkları (veritabanı, servisler) mock nesneleri ile simüle etmelisiniz.
Örnek bir CreateUserCommandHandler testi için Moq ve FluentAssertions kütüphanelerini kullanan temel bir senaryo aşağıdadır:
[Fact]
public async Task Handle_ValidCommand_ReturnsUserId()
{
// Arrange
var mockRepo = new Mock();
var handler = new CreateUserCommandHandler(mockRepo.Object);
var command = new CreateUserCommand { Username = "testuser", Email = "test@test.com" };
// Act
var result = await handler.Handle(command, CancellationToken.None);
// Assert
result.Should().NotBe(Guid.Empty);
mockRepo.Verify(x => x.AddAsync(It.IsAny()), Times.Once);
}
Testlerinizi yazarken dikkat etmeniz gereken en kritik nokta, Handler'ın sadece kendi sorumluluğunu (örneğin veritabanına kayıt atma) yerine getirip getirmediğini doğrulamaktır. MediatR'ın kendi çalışma zamanı (runtime) test edilmemelidir; çünkü kütüphanenin kendisi zaten kapsamlı testlerden geçmiştir.
İleri Seviye İpuçları: MediatR İçin "Notification" Kullanımı
Bazen bir komut işlendikten sonra, sistemin diğer parçalarını bilgilendirmek istersiniz. Örneğin, bir sipariş oluşturulduğunda e-posta gönderilmesi veya stok miktarının güncellenmesi gibi işlemler "Domain Event" olarak adlandırılır. MediatR'ın INotification arayüzü, bu tür "yayınla-abone ol" (pub/sub) senaryoları için biçilmiş kaftandır.
Bir komut işlendikten sonra birden fazla bağımsız işleyiciyi tetiklemek için şu yapıyı kullanabilirsiniz:
public class OrderCreatedNotification : INotification
{
public Guid OrderId { get; set; }
}
public class SendEmailHandler : INotificationHandler
{
public async Task Handle(OrderCreatedNotification notification, CancellationToken cancellationToken)
{
// E-posta gönderme mantığı buraya gelir
await Task.CompletedTask;
}
}
// Komut işleyicisi içerisinde tetikleme:
await _mediator.Publish(new OrderCreatedNotification { OrderId = order.Id });
Bu yaklaşım, ana komut işleyicinizin (CommandHandler) e-posta servisi veya stok servisi gibi yan bağımlılıklardan arınmasını sağlar. Böylece "Single Responsibility" (Tek Sorumluluk) prensibini daha sıkı bir şekilde korumuş olursunuz.
Performans ve Ölçeklenebilirlik Notları
- Handler'ları Küçük Tutun: Bir Handler sınıfı çok fazla bağımlılık (constructor injection) alıyorsa, muhtemelen çok fazla iş yapıyordur. Bu durumda mantığı parçalara ayırın.
- Async/Await Kullanımı: MediatR ile çalışırken tüm I/O işlemlerinde (veritabanı, API çağrıları) mutlaka
asyncmetotları tercih edin; aksi halde thread havuzunuzda darboğaz oluşabilir. - Kayıt (Registration) Performansı: Çok büyük projelerde
MediatR.Extensions.Microsoft.DependencyInjectionkullanırken, tüm handler'ları tek tek kaydetmek yerineAddMediatR(typeof(Startup))kullanarak assembly taraması yapılması önerilir.
Sonuç
C# ile MediatR kullanarak CQRS uygulamak, uygulamanızı daha modüler, okunabilir ve test edilebilir hale getirir. Bu makalede adım adım komutların ve sorguların nasıl ayrılacağını, handler'ların nasıl oluşturulacağını ve DI ile nasıl entegre edileceğini inceledik. Bir sonraki adım olarak, projenize IPipelineBehavior ekleyerek otomatik doğrulama (validation) ve loglama mekanizmalarını merkezi hale getirmeyi deneyebilirsiniz.
Sorumluluk Reddi: Bu rehberdeki kod örnekleri eğitim amaçlıdır. Üretim ortamında (production) kullanmadan önce, veritabanı bağlantı dizgelerini (connection strings) güvenli bir şekilde (Azure Key Vault veya Environment Variables) saklayın ve tüm uç noktalar için yetkilendirme (authorization) kontrollerini eksiksiz yapın.


Yorumlar (0)
Yorum Yaz