Yüksek işlem hacimli ve yüksek ölçekli uygulamaların dünyasında, geleneksel CRUD (Oluştur, Oku, Güncelle, Sil) mimarileri genellikle bir duvara çarpıyor. Temel model basit olsa da, okuma ve yazma işlemlerinin aynı veri yapısını ve optimizasyon kısıtlamalarını paylaşmasını zorunlu kılıyor. Komut Sorgu Sorumluluğu Ayrımı (CQRS), bir uygulamanın okuma ve yazma taraflarını ayrı modellere bölerek bu sorunu çözen bir kalıptır. Bu yaklaşım, her iki tarafı bağımsız olarak optimize etmenizi sağlayarak daha iyi performans, ölçeklenebilirlik ve sürdürülebilirlik elde etmenize olanak tanır.
CQRS Nedir?
CQRS, yazılım mimarisinde veri modelini ikiye bölen bir ilkedir: Yazma (değişiklik) işlemlerini yönetmek için bir Komut modeli ve okuma (erişim) işlemlerini yönetmek için bir Sorgu modeli. Temel fikir basittir: Durumu değiştiren işlemler (Komutlar) veri döndürmemeli ve veri getiren işlemler (Sorgular) durumu değiştirmemelidir.
Bu ayrım, okuma ve yazma taraflarının, kullanım senaryolarına özel olarak uyarlanmış farklı veritabanı şemalarına, depolama teknolojilerine veya hatta barındırma altyapılarına sahip olmasına olanak tanır.
Neden CQRS Kullanılır?
- Performans Optimizasyonu: Yazma modelinin bütünlüğünü etkilemeden, hızlı sorgular için okuma modelini normalleştirmemiş (denormalize) olarak kullanabilirsiniz.
- Ölçeklenebilirlik: Okuma ağırlıklı uygulamalar, okuma tarafını yazma tarafından bağımsız olarak ölçeklendirebilir.
- Güvenlik: Yazma işlemleri sıkı bir şekilde kontrol edilebilir ve denetlenebilirken, okuma işlemleri açık ve hızlı kalabilir.
- Karmaşıklık Yönetimi: Karmaşık alanlarda, her şeyi tek bir dev varlıkta yöneten "Tanrı Modeli" (God Model) anti-kalıbını önlemek için endişeleri ayırmak faydalıdır.
Mimari Genel Bakış
Tipik bir CQRS uygulamasında akış şu şekildedir:
- Bir Komut, Komut tarafına gönderilir.
- Komut tarafı, iş mantığını doğrular ve işler, olayları bir Yazma Deposuna (Write Store) kalıcı olarak kaydeder.
- Olaylar, bir Olay Otobanına (Event Bus) veya Mesaj Kuyruğuna (Message Queue) yayınlanır.
- Sorgu tarafındaki işleyiciler (handlers) bu olayları tüketir ve Okuma Deposunu (genellikle bir NoSQL veritabanı veya arama dizini) günceller.
- Bir Sorgu, Sorgu tarafına gönderilir ve bu taraf, Okuma Deposundan önceden hesaplanmış verileri getirir.
Kod Örneği
Aşağıda, Komutların ve Sorguların ayrımını gösteren basitleştirilmiş bir C# örneği bulunmaktadır.
// 1. Bir Komut Tanımla (Yazma İşlemi)
public record CreateOrderCommand : ICommand<Guid>
{
public Guid CustomerId { get; init; }
public decimal Amount { get; init; }
}
// 2. Bir Sorgu Tanımla (Okuma İşlemi)
public record GetOrderByIdQuery : IQuery<OrderDto>
{
public Guid OrderId { get; init; }
}
// 3. Komut İşleyicisi (Gerçek kaynağa yazar)
public class CreateOrderCommandHandler : ICommandHandler<CreateOrderCommand, Guid>
{
private readonly IOrderRepository _repo;
private readonly IEventPublisher _publisher;
public async Task<Guid> Handle(CreateOrderCommand command, CancellationToken ct)
{
// İş Mantığı ve Doğrulama
var order = new Order(command.CustomerId, command.Amount);
// Yazma Deposuna Kaydet (İlişkisel DB)
await _repo.SaveAsync(order, ct);
// Alan Olayını Yayınla
await _publisher.PublishAsync(new OrderCreatedEvent(order.Id), ct);
return order.Id;
}
}
// 4. Sorgu İşleyicisi (Optimize edilmiş depodan okur)
public class GetOrderByIdQueryHandler : IQueryHandler<GetOrderByIdQuery, OrderDto>
{
private readonly IOrderReadRepository _readRepo;
public async Task<OrderDto> Handle(GetOrderByIdQuery query, CancellationToken ct)
{
// NoSQL/ES/Önbellekten Oku
return await _readRepo.GetByIdAsync(query.OrderId, ct);
}
}
Pratik Düşünceler
CQRS güçlü bir araç olsa da karmaşıklık getirir. Okuma tarafının yazma tarafının gerisinde kalabileceği için nihai tutarlılığı (eventual consistency) yönetmeniz gerekir. Ayrıca, yazma ve okuma modelleri arasındaki senkronizasyonu sürdürmek, sağlam olay işleme ve izleme gerektirir. Tamamen ayrı veri depolarına geçmeden önce, CQRS'ye aynı veritabanını ancak farklı görünümleri (views) kullanarak "Okuma-Yazma ayrımı" yaklaşımı olarak başlamanız önerilir.
Sonuç
CQRS, her uygulama için sihirli bir çözüm değildir. Yüksek yazma işlem hacmine, karmaşık alan mantığına veya belirli okuma performans gereksinimlerine sahip sistemler için en uygunudur. Komutları ve sorguları ayırarak, sistem bileşenlerinizi bağımsız olarak ölçeklendirmek ve optimize etmek için esneklik kazanırsınız. Uygulamanızın karmaşıklığı arttıkça, CQRS'yi benimsemek, daha sağlam ve ölçeklenebilir bir mimariye doğru stratejik bir adım olabilir.