Giriş
Yazılım dünyasında kodun anlaşılır, esnek ve sürdürülebilir olması, özellikle ölçeklenen projelerde büyük fark yaratır. Sıkça karşılaştığımız “bir fonksiyon hem veri getiriyor hem de sistemi değiştiriyor” gibi pratikler, projede büyüdükçe karmaşaya yol açabilir. İşte burada Command Pattern ve CQRS devreye girer.
Bu yazıda şunları bulacaksınız:
-
Command Pattern’in temeli ve motivasyonu
-
“Komut” ile “Sorgu”yun neden ayrılması gerektiği
-
CQRS yaklaşımının nasıl çalıştığı
-
Gerçek hayat senaryoları ve detaylı kod örnekleri
-
Avantajları, dezavantajları ve uygulama önerileri

Command Pattern Nedir?
Temeli ve Amacı
Command Pattern (Komut Deseni), nesne tabanlı programlamada davranışsal bir tasarım desenidir.
Ana fikir: Her işlemi (örneğin bir “kaydet”, “güncelle”, “sil” işlemini), kendi nesnesi haline getirerek kodunuzu gevşek bağlı ve genişletilebilir yapmaktır.
Neden Command Pattern?
-
Sıkı Bağlılığı Azaltır: İşlemleri çağıran kod, işlemin nasıl yapıldığını bilmek zorunda kalmaz.
-
Undo/Redo Kolaylığı: Her komut kendi durumunu saklayabildiği için işlemleri geri almak mümkündür.
-
Komutları Sıraya Almak: Komutları bir kuyruğa ekleyip daha sonra topluca çalıştırmak kolaylaşır.
-
Düzenli Kod: Özellikle UI, API veya arka planda birden fazla işlem birbirine bağlıysa, karmaşık kodu sadeleştirir.
UML Diyagramı ve Temel Yapı
Temel Yapı:
-
Command (Komut Arayüzü): Tüm komutların ortak arayüzü.
-
ConcreteCommand (Gerçek Komut): Gerçek işlemi yapan sınıf.
-
Invoker (Çağırıcı): Komutları tetikleyen nesne (UI, bir event handler vs).
-
Receiver (Alıcı): İşin gerçek yapıldığı nesne (ör: veritabanı sınıfı).
// Komut arayüzü
public interface ICommand {
void Execute();
}
// Gerçek komut
public class SaveOrderCommand : ICommand {
private OrderService _orderService;
public SaveOrderCommand(OrderService orderService) {
_orderService = orderService;
}
public void Execute() {
_orderService.SaveOrder();
}
}
// Çağırıcı
public class OrderController {
private ICommand _saveOrderCommand;
public OrderController(ICommand saveOrderCommand) {
_saveOrderCommand = saveOrderCommand;
}
public void OnSaveButtonClicked() {
_saveOrderCommand.Execute();
}
}
Pratikte:
Sipariş kaydetme, kullanıcıya bildirim gönderme, e-posta atma, dosya silme gibi işlemler her biri birer komut nesnesi olarak yönetilebilir.
Sorgu (Query) ve Komut (Command) Arasındaki Farklar
Bir kod parçası ya da bir endpoint, ya veri getirir (query) ya da veri değiştirir (command).
Her iki işlevi aynı anda yapmaya çalışmak (ör: bir kullanıcı güncellenirken aynı fonksiyonla kullanıcıyı döndürmek) kodu karmaşıklaştırır.
| Komut (Command) | Sorgu (Query) | |
|---|---|---|
| Amaç | Sistemi değiştirmek | Sistemden veri getirmek |
| Örnek | Sipariş oluştur, Profil güncelle | Sipariş listesini getir, K. bakiyesi sorgula |
| Etki | Yan etki yaratır (değişiklik yapar) | Yan etki yaratmaz (sadece okur) |
| Sonuç | Genellikle boş veya işlem sonucu döner | Mutlaka veri döndürür |
Pratikte Neden Ayrılmalı?
-
Test Edilebilirlik: Komutlar test edilirken sistemin değiştiğinden, sorgular ise veri değişmediğinden emin olabilirsin.
-
Bakım Kolaylığı: İş mantığı daha kolay anlaşılır.
-
Performans: Sorgular cache’lenebilir, komutlar event’lerle işlenebilir.
CQRS (Command Query Responsibility Segregation) Nedir?
Kısaca CQRS
CQRS: Komut ve sorguların farklı yollarla işlendiği bir mimari yaklaşımdır.
“Komutlar ayrı, sorgular ayrı; kod, model ve veri yolları tamamen birbirinden ayrılır.”
Neden Gelişti?
Daha büyük, karmaşık uygulamalarda aşağıdaki sorunlar ortaya çıkıyor:
-
Bir fonksiyon çok fazla iş yapıyor: hem veri değiştiriyor, hem veri döndürüyor.
-
Sistem büyüdükçe kodun mantığı bozuluyor, herkes kodu anlamakta zorlanıyor.
-
Performans sıkıntıları (ör: büyük tablolardan veri okurken aynı anda yazma yapmak).
CQRS, bu problemleri çözmek için ortaya çıktı.
Temel Prensipler
-
Komut ve Sorgular Farklı Modeller Kullanır:
-
Komutlar (write model): Sistemi değiştiren işlemler (ör. CreateUserCommand).
-
Sorgular (read model): Sistemi değiştirmeyen, sadece okuyan işlemler (ör. GetUserListQuery).
-
-
Ayrı Katmanlar:
-
Farklı veri modelleri, farklı servisler kullanılabilir.
-
-
Yüksek Performans ve Ölçeklenebilirlik:
-
Okuma ve yazma ayrı ölçeklenir.
-

CQRS’in Uygulamada Artı ve Eksileri
Avantajları
-
Net Sorumluluk: Kodun hangi kısmının veri değiştirdiği, hangisinin veri getirdiği netleşir.
-
Performans: Okuma ve yazma için ayrı veri kaynağı ve servis kullanmak mümkün.
-
Güvenlik: Sorgular sadece okunabilir, komutlar sadece değişiklik yapabilir.
-
Mikroservislerde Çok Uyumlu: Özellikle DDD (Domain Driven Design) ve Event Sourcing ile iyi entegre olur.
Dezavantajları
-
Ekstra Karmaşıklık: Basit uygulamalarda gereksiz iş yükü yaratır.
-
Senkronizasyon Sorunu: Okuma ve yazma modelleri arasında tutarlılık (consistency) yönetmek gerekebilir.
-
Kod Fazlalığı: Basit işlemler için bile komut ve sorgu objeleri yazmak gerekebilir.
Gerçek Hayattan Uygulama Senaryosu
Senaryo: Online Kitap Satış Uygulaması
Komutlar:
-
Kitap Ekle (AddBookCommand)
-
Sipariş Oluştur (CreateOrderCommand)
-
Stok Güncelle (UpdateStockCommand)
Sorgular:
-
Tüm kitapları listele (GetBooksQuery)
-
Bir siparişin detayını getir (GetOrderDetailQuery)
-
Belirli bir yazarın kitaplarını getir (GetBooksByAuthorQuery)
Neden Ayrı Tutarız?
Bir kullanıcı yeni bir kitap eklerken, başka bir kullanıcı o anda kitap listesini sorguluyor olabilir. Komut işlemi, stok ve fiyat gibi verileri değiştirirken; sorgu işlemi hızlıca kitap listesini okur.
Bu sayede okuma işlemi yazmadan etkilenmez ve sistem daha hızlı cevap verir.
// Komut - Sipariş oluştur
public class CreateOrderCommand {
public int UserId { get; set; }
public List<int> BookIds { get; set; }
public void Execute() {
// Sipariş kaydetme işlemi
// Stok güncelle
// Bildirim gönder
}
}
// Sorgu - Sipariş detayını getir
public class GetOrderDetailQuery {
public int OrderId { get; set; }
public Order Execute() {
// Veritabanından detayları oku, döndür
}
}
Senaryoda Dikkat Edilecek Noktalar
-
Sipariş oluşturulduktan sonra stok düşürülmezse “tükenen ürün” hatası yaşanabilir.
-
CQRS’te, okuma modeliyle yazma modeli farklı zamanlarda güncellenmiş olabilir. Bu durumda kullanıcının “güncel stok” görmesi gecikebilir. Buna eventual consistency denir.
Command Pattern ve CQRS Nasıl Birlikte Kullanılır?
-
Command Pattern, CQRS’in komut (write) tarafında genellikle kullanılır.
-
Komutları nesne olarak modeller, handler ile işler.
-
Sorgu tarafında ise genelde query nesneleri ve handler’lar olur.
Örnek CQRS Handler ile Command Pattern:
public interface ICommand { }
public class CreateOrderCommand : ICommand {
// Parametreler
}
public class CreateOrderHandler {
public void Handle(CreateOrderCommand command) {
// Sipariş oluşturma işlemleri burada
}
}
Uygulamada Karşılaşılan Sorunlar ve Çözüm Yöntemleri
Senkronizasyon Problemi
-
Komut ile yazma işlemi yapılır, sorgu modeli hemen güncellenmeyebilir.
-
Çözüm: Event tabanlı altyapı (event bus), background worker kullanımı.
Transaction Yönetimi
-
Komutlar arka arkaya birden fazla sistemi güncelleyebilir.
-
Çözüm: Saga Pattern, transaction outbox gibi yapılarla atomiklik sağlanabilir.
Test Edilebilirlik
-
Komutlar “yan etkili” olduğu için, mock ve unit test ile denetlenmeli.
-
Sorgular ise rahatça mocklanıp test edilebilir.
Best-Practice ve Tavsiyeler
-
Küçük uygulamalarda CQRS’e gerek yok; örnek olarak admin panelde, basit CRUD için kullanma.
-
Komut ve sorgu modellerini ayrı katmanlar/dosyalarda tut.
-
Komut işlemlerinde validasyon (örneğin FluentValidation ile) ve logging ekle.
-
Sorgularda performans için cache kullanmak faydalı.
Sıkça Sorulan Sorular (FAQ)
CQRS küçük projelerde neden önerilmez?
Ekstra soyutlama ve model karmaşası yaratır. Basit CRUD uygulamalarında gereksizdir.
Command Pattern başka nerelerde kullanılır?
Undo/redo işlemlerinde, task scheduler, event sourcing, workflow otomasyonunda.
Eventual Consistency nedir?
Komut ve sorgu modelleri arasında kısa süreli tutarsızlık olabilir. Sistem sonunda tutarlı olur, bu normaldir.
Komut nesneleri çoğalınca nasıl yönetilir?
Her komutu ve handler’ı ayrı dosyada, uygun namespace ile düzenle. Gerekirse otomatik discovery veya dependency injection kullan.
Sonuç
Command Pattern ve CQRS, büyük ölçekli ve karmaşık sistemlerde kodun okunabilirliğini, sürdürülebilirliğini ve performansını artırmak için birebirdir.
Küçük projelerde gereksiz karmaşıklık yaratabilir, ancak domain’e göre gerektiğinde kademeli şekilde entegre edilebilir.
Kendi projende önce küçük bir modülde deneyerek başlamanı öneririm.