Geleneksel yazılım mühendisliğinde veriyi yaratma, okuma, güncelleme ve silme (CRUD) operasyonları etrafında şekillenen yaklaşım, uygulamanın tüm okuma ve yazma yükünü tek bir ilişkisel veritabanı şemasına hapseder. Düşük trafikli sistemlerde bu mimari sorunsuz çalışsa da, saniyede on binlerce işlemin gerçekleştiği kurumsal ölçekli sistemlerde operasyonel bir darboğaza dönüşür.

Veritabanı motoru, karmaşık analitik sorgulara yanıt vermekle sisteme milisaniyeler içinde yeni veri yazmak arasında amansız bir kaynak rekabetine girer. Yazma hızını artırmak için indeksleri silmek, okuma hızını artırmak için ise tabloya yeni indeksler eklemek gerekir ki bu, tek bir veritabanı topolojisi içinde verimli şekilde çözülemeyecek bir mühendislik paradoksudur. Bu kısıtlamayı aşmak için veri yaşam döngülerini izole eden CQRS mimarisine ve sistemin hafızasını yeniden tanımlayan Event Sourcing yapılarına geçiş yapmak stratejik bir gerekliliktir.

CQRS ile Mantıksal ve Fiziksel Ayrışma

CQRS, sisteme veri yazan command modelleri ile sistemden veri okuyan query modellerini birbirinden ayırır. Özünde CQRS, read ve write modellerini mantıksal olarak ayırır; ölçeklenebilirlik ve performans ihtiyaçları doğrultusunda bu veri depoları fiziksel olarak da ayrılabilir.

Yazma işlemleri, veri eklemeye ve doğrulamaya optimize edilmiş bir modele yapılır. Okuma işlemleri ise, kompleks join operasyonlarına gerek bırakmayacak şekilde önceden denormalize edilmiş özel yapılardan çekilir. Bu mimaride, kullanıcıların yaptığı ağır arama filtrelemeleri operasyonel akışı bloklamaz. Yazma ve okuma işlemleri aynı veritabanı kaynakları için rekabet etmez. CQRS'in sunduğu bu operasyonel esneklik, Event Sourcing ile birleştiğinde maksimum potansiyeline ulaşır.

Event Sourcing ve Verinin Mutlak Tarihçesi

İlişkisel veritabanlarında mevcut durum (state) kaydedilir. Veri güncellendiğinde, UPDATE komutuyla eski veri ezilir ve tarihsel bağlam kaybolur. Event Sourcing ise veritabanını bir durum saklama yeri olmaktan çıkarıp, sistemi değişmez olayların ardışık olarak eklendiği bir Append-Only Log yapısına dönüştürür. Sistemde DELETE veya UPDATE komutları barınmaz.

Bir adres değiştiğinde eski adres silinmez; olay defterinin sonuna AddressChangedEvent adında yeni bir kayıt eklenir. Sistemin güncel durumunu bulmak için, tüm olaylar sırasıyla hafızada tekrar oynatılır. Event Sourcing, mevcut durumun kendisini değil, o duruma nasıl gelindiğinin mutlak ispatını tutar. Bu sayede güçlü ve yeniden oluşturulabilir bir audit trail sağlar. Eğer sistemde kriptografik olarak manipüle edilemez (tamper-proof) bir kayıt yapısı hedefleniyorsa, event log üzerine hash-chain veya immutable (WORM) depolama gibi ek mekanizmaların entegre edilmesi gerekir.

Event Store ve Optimistic Concurrency Control

Event Sourcing mimarisinde yazma katmanındaki veritabanına Event Store denir. Temel görevi, gelen domain event'leri benzersiz bir stream ID (genellikle Aggregate Root ID) altında ardışık olarak diske yazmaktır. Yüksek eşzamanlılığa (concurrency) sahip kurumsal sistemlerde veri bütünlüğünü korumak için Event Store üzerinde Optimistic Concurrency Control uygulanır.

Her olayın bir versiyon numarası vardır. Command işlenirken Aggregate'in hafızadaki versiyonu ile veritabanına yazılmak istenen beklenen versiyon karşılaştırılır. Eğer veritabanındaki versiyon daha büyükse, araya başka bir işlemin girdiği doğrulanır ve sistem ConcurrencyException fırlatarak işlemi reddeder. Bu yapı, lock kullanmadan aynı stream üzerindeki lost update ve conflicting append problemlerini başarıyla önler; ancak birden fazla aggregate'i ilgilendiren kompleks iş kurallarını (business invariants) tek başına garanti edemez.

Projeksiyonlar: Okuma Modellerini İnşa Etmek

Event Store yüksek yazma hızları sunsa da, okuma işlemleri için yapısal zorluklar içerir. Event Store'lar stream ID, event type, category veya metadata üzerinden sorgulanabilse de, genel amaçlı sorgular ve kullanıcıya yönelik karmaşık read workload'ları için uygun değildir.

Bu noktada CQRS'in query katmanı devreye girer. Event Store'a yazılan her bir olay arka plan mekanizmaları ile dinlenir ve farklı veritabanlarında özel okuma modelleri inşa edilir (projeksiyon). Tek bir OrderCreatedEvent üzerinden aynı anda Elasticsearch'te full-text search dökümanı, Redis'te düşük latency erişimi için key-value eşleşmesi ve PostgreSQL'de ilişkisel bir tablo oluşturulabilir. Okuma modelleri atılabilir (disposable) yapılardır. Okuma veritabanı çökse bile, Event Store'daki olaylar tekrar oynatılarak read katmanı sıfırdan yeniden inşa edilebilir.

Tekrarlı İletim ve Idempotency (Duplicate Delivery)

Projeksiyon katmanındaki en kritik gereksinimlerden biri tekrarlı mesaj iletiminin (duplicate delivery) yönetilmesidir. Olay güdümlü (event-driven) mimariler genellikle "at-least-once" (en az bir kere) teslimat garantisi ile çalıştığı için, projeksiyon mantığı mutlaka idempotent (aynı işlemin birden fazla uygulanmasının sonucu değiştirmemesi) handler'lar içermelidir.

Teknik düzeyde bu durum, mesajdaki eventId veya sequence number (sıra numarası) değerinin, okuma veritabanında tutulan bir checkpoint (kontrol noktası) ile karşılaştırılmasıyla yönetilir. Yeniden iletilen olayların okuma modelini bozmaması için, read modelin güncellenmesi ve checkpoint'in ilerletilmesi işlemlerinin atomik olarak gerçekleştirilmesi şarttır.

Senkronizasyon Köprüsü ve Projection Lag

Yazma ve okuma veritabanları arasındaki senkronizasyonu kurgulamak, kompleks mimari ödünleşimler (trade-off) barındırır. Event Store'a yazılan bir olayın okuma modeline yansıması arasındaki zaman farkına Projection Lag denir. Read model asenkron güncelleniyorsa, sistem Eventual Consistency ile çalışır ve kullanıcının anında en güncel veriyi göreceğinin kesin bir garantisi yoktur.

Senkron projection her durumda bir anti-pattern değildir; güçlü bir read-after-write tutarlılığı gerektiren spesifik işlem sınırları içinde bilinçli bir tercih olabilir. Ancak senkron projection mimarisi, sistemin ölçeklenebilirliğini ciddi şekilde sınırlar. Ayrıca, tamamen ayrı kaynaklara (örneğin aynı kod bloğunda hem Postgres hem Redis'e) yapılan non-transactional dual-write işlemleri, sistemde büyük veri tutarsızlığı riskleri (consistency risks) yaratır.

RabbitMQ veya Kafka gibi Message Broker kullanan push tabanlı mimariler, aktif olay dağıtımı yaparak doğrudan polling yükünü ortadan kaldırır. Outbox Pattern ile birleştirildiğinde dual-write anomalisini çözer ve güvenilir mesaj iletimi sağlar, ancak sistem tasarımına ek katmanlar dahil eder.

CDC ve WAL İle İleri Düzey Entegrasyon

Operasyonel ayrışmanın kritik olduğu yüksek ölçekli sistemlerde CDC (Change Data Capture) stratejisi ileri düzey bir entegrasyon sunar. Debezium gibi araçlarla, veritabanının fiziksel Write-Ahead Log (WAL) dosyaları doğrudan okunur.

CDC, uygulama sorgularından bağımsız değişiklik yakalama sağlar ve veritabanı CPU'su üzerinde oldukça düşük bir ek yükle çalışabilir. Ancak; logical decoding, replication slot yönetimi ve WAL retention ayarlarının yapılandırılması, birincil veritabanı üzerinde belirli bir operasyonel ve depolama maliyeti yaratır. Değişikliklerin disk seviyesinde yakalanıp event bus'lara aktarılması, komut ve sorgu yaşam döngüleri arasında gelişmiş bir fiziksel izolasyon sağlar.

Performans Darboğazı ve Snapshotting

Event Sourcing'de mevcut durumu hesaplamak için o Aggregate altındaki tüm olayları bellek üzerinde tekrar oynatmak (rehydration) gerekir. Uzun ömürlü ve on binlerce eventi barındıran bir Aggregate'e yeni komut geldiğinde, tüm eventleri veritabanından çekip hafızada işlemek CPU ve network kaynaklarını tüketerek operasyonel gecikmelere yol açar.

Bu darboğaz Snapshotting mekanizması ile çözülür. Belirli aralıklarla Aggregate'in hesaplanmış state'i farklı bir tabloya snapshot olarak kaydedilir. Sistem yeni bir command işlerken en son kaydedilen snapshot'ı memory'e yükler ve sadece o noktadan sonraki yeni eventleri işleyerek Event Sourcing altyapılarında yüksek performans sağlar.

Şema Evrimi ve Upcasting Deseni

Event Sourcing'de geçmiş olayların değiştirilemez (immutable) yapısı, Şema Evrimi (Schema Evolution) sürecini zorlaştırır. OrderCreatedEvent yapısına yeni bir zorunlu alan eklendiğinde, veritabanındaki eski formatlı milyonlarca olayı yönetmek için Upcasting deseni kullanılır.

Eski versiyon bir event okunduğunda, Upcaster katmanı bu eventi araya girerek hafıza üzerinde manipüle eder, eksik alanları varsayılan değerlerle doldurur ve yeni versiyon formatına dönüştürerek iş mantığına sunar. Bu "on-the-fly" (çalışma zamanlı) dönüşüm sayesinde veritabanındaki orijinal veri asla bozulmaz.

Kritik lojistik süreçlerin, finansal işlemlerin ve yüksek trafiğe sahip otonom akışların bulunduğu Core Domain'lerde, yalnızca geleneksel ilişkisel yapılara bağlı kalmak sistemin ölçeklenebilirliğini engeller. Okuma ve yazma operasyonlarını iş yükünün gereksinimlerine göre yapılandırmak ve yeniden oluşturulabilir bir mutlak veri tarihçesi kurmak, kurumsal veri yüklerine karşı sağlam bir temel oluşturur. Bir sistemin mevcut durumunu bilmek temel bir gereksinimdir; o duruma milisaniyesine kadar nasıl geldiğini kanıtlayabilmek ise modern mimarinin sunduğu en belirleyici operasyonel güçtür.