Geleneksel monolitik mimarilerde veri tutarlılığı, ilişkisel veritabanının sunduğu ACID garantileri sayesinde tek bir transaction bloğu içerisinde zahmetsizce sağlanır. Uygulama veritabanına bağlanır, business logic işletilir, tablolar güncellenir ve işlem commit edilir. Sistem dağıtık bir topolojiye, özellikle mikroservisler veya event-driven mimarilere taşındığında ise bu konfor alanı tamamen ortadan kalkar. Domain-Driven Design prensipleri gereği her mikroservisin kendi Bounded Context sınırı ve kendine ait izole bir veritabanı olması gerekir. Mikroservislerde merkezi single source of truth yapısı parçalandığında, sistemin veri bütünlüğünü korumak devasa bir mühendislik problemine dönüşür.

Veri bütünlüğünü dağıtık ağlarda sağlamak için Two-Phase Commit veya XA Transactions gibi senkron dağıtık transaction protokollerini kullanmak; ağ gecikmeleri, servisler arası tight coupling ve uzun süren veritabanı lock mekanizmaları nedeniyle sistemin yatayda ölçeklenebilirliğini durma noktasına getirir. CAP teoreminin matematiksel gerçekliği gereği, network partition riski taşıyan dağıtık bir sistemde aynı anda hem Strong Consistency hem de Availability garanti edilemez.

Bu darboğazı aşmak için modern kurumsal mühendislik yaklaşımları, BASE felsefesini benimseyerek Eventual Consistency modeline odaklanır. Bu modelde verinin servisler arasında güvenli, hatasız, kayıpsız ve doğru sırada aktığını garanti altına almak; CQRS, Event Sourcing, Outbox, CDC ve Saga gibi spesifik tasarım desenlerinin doğru mimari katmanlarda case by case orkestrasyonunu zorunlu kılar.

Dağıtık Sistemlerde Tutarlılık Paradoksu

Event-driven mimarilerde servislerin birbiriyle asenkron olarak haberleşmesi, veri senkronizasyonu konusunda kritik zafiyet potansiyelleri taşır. Servislerin kendi iç state'lerini güncelleyip dış dünyaya event fırlatması arasındaki zaman aralığı, sistemin en kırılgan noktasıdır.

Case 1: Dual-Write Anomalisi ve Phantom Data

Durum: Bir sipariş mikroservisi, gelen HTTP request'i işleyip kendi ilişkisel veritabanında siparişin state'ini güncelledikten hemen sonra, bu değişikliği envanter ve fatura servislerine duyurmak için doğrudan Kafka veya RabbitMQ'ya event fırlatır. Uygulama kodunda arka arkaya yazılmış iki satırlık bir I/O işlemidir: Önce db.save(), ardından broker.publish().

Kritik Risk: Veritabanı commit işlemi başarılı olduktan sadece birkaç milisaniye sonra uygulamanın çalıştığı pod çökerse veya Kafka cluster'a giden ağ bağlantısında bir timeout yaşanırsa, mesaj broker'a iletilemez. İşlemi başlatan sipariş servisi güncel veriye sahipken, sistemin geri kalanı bu değişiklikten tamamen habersiz kalır. Kullanıcıya siparişin alındığı bilgisi dönülür ancak arka planda süreç işlemez. Sistemde kalıcı bir phantom data tutarsızlığı oluşur. İşlemlerin sırası değiştirilip önce broker'a mesaj atılır sonra db'ye yazılmaya çalışılırsa, bu sefer de db transaction fail olduğunda sisteme asılsız bir event fırlatılmış olur.

Çözüm Mimarisi: Transactional Outbox ve CDC Entegrasyonu

Bu asimetrik hata durumunu çözmek için uygulama kodunda Transactional Outbox Pattern mimari bir standart olarak uygulanır. İş mantığı çalıştırıldığında, güncellenen state verisi ve fırlatılacak olan domain event, veritabanında aynı atomik transaction içerisinde diske yazılır. Payload, metadata ve event type bilgilerini içeren event objesi, asıl iş tablosunun yanındaki özel olarak oluşturulmuş bir Outbox tablosuna insert edilir. Eğer event diske yazılamazsa tüm transaction rollback olur ve veri bütünlüğü korunur.

Outbox tablosunda biriken olayları broker'a güvenle iletmek için Debezium gibi CDC araçları devreye girer. Polling tabanlı worker'ların aksine CDC, veritabanının query engine katmanını tamamen by-pass eder. PostgreSQL ortamında pg_recvlogical üzerinden logical decoding yaparak fiziksel Write-Ahead Log (WAL) dosyalarını doğrudan okur. Değişiklikler, veritabanı CPU'suna minimum ek yük bindirilerek at-least-once teslimat garantisiyle event bus'a aktarılır. Dual-Write problemi donanım seviyesinde çözülür. Ancak bu mimarinin operasyonel bedeli; veritabanı üzerinde replication slot yönetimi, WAL retention disk maliyetleri ve connector'ların bakım yüküdür.

Event Sourcing: State Yerine Davranış Kaydı

Sistemin mevcut durumunu sadece son halini veritabanına yazarak saklamak, verinin o duruma hangi karmaşık business rule'lardan geçerek geldiğini kalıcı olarak silmektir. Yüksek regülasyona tabi finans, sigorta veya lojistik gibi core domain yapılarında intent kaybı kabul edilemez.

Case 2: Lost Update ve Lock Darboğazı

Durum: Eşzamanlı trafiğin çok yüksek olduğu bir e-cüzdan sisteminde, aynı Aggregate Root üzerinde saniyede yüzlerce para ekleme ve çekme isteği asenkron olarak işlenmeye çalışılır.

Kritik Risk: Geleneksel mimarilerde bu eşzamanlı çakışmaları önlemek için row-level lock veya pessimistic concurrency control kullanılır. Ancak bu veritabanı kilitleri, I/O kapasitesini ciddi şekilde tüketerek sistemde devasa bir bottleneck yaratır. Lock mekanizmaları devre dışı bırakıldığında ise read-modify-write döngüleri birbirini ezerek lost update problemine yol açar. İşlemler başarılı görünmesine rağmen kullanıcının bakiyesi yanlış hesaplanır.

Çözüm Mimarisi: Append-Only Log ve Optimistic Concurrency Control

Event Sourcing, veri yönetimini kökten değiştirerek state değişimlerini immutable domain event'leri olarak Event Store'a ardışık olarak ekler. Veritabanında hiçbir zaman doğrudan UPDATE veya DELETE komutu çalıştırılmaz. İşlemin iptal edilmesi bile TransactionReverted adında yeni bir event olarak işlenir. Event Store'a yazılan her kayıt; StreamId, CorrelationId, CausationId ve Timestamp bilgilerini içeren standart bir Event Envelope yapısıyla kaydedilir. Sistemin güncel hali, geçmiş event'lerin memory'de rehydration işlemi ile baştan sona oynatılarak hesaplanır.

Lock darboğazını aşmak için Event Store düzeyinde Optimistic Concurrency Control işletilir. Her event'in ait olduğu stream içerisinde monotonik olarak artan eşsiz bir sequence numarası vardır. Uygulama bir command'i işlerken Event Store'dan stream'i çeker ve o anki expected version değerini not eder. İş mantığı tamamlanıp üretilen yeni olay diske yazılmadan önce, bellekteki expected version ile veritabanındaki güncel stream versiyonu karşılaştırılır. Araya başka bir node'dan işlem girmişse, sistem anında ConcurrencyException fırlatarak işlemi reddeder. Command handler bu hatayı yakalayıp işlemi güvenli bir şekilde retry mekanizmasına devreder. Pahalı kilitler kullanılmadan çok yüksek bir throughput sağlanır.

Veritabanı Seçimleri ve Broker Yanılgıları

Sektörde Event-Driven mimariler kurgulanırken sıkça yapılan en büyük mimari hatalardan biri, message broker'ların doğrudan bir veritabanı gibi konumlandırılmasıdır.

Case 3: Message Broker'ları Event Store Olarak Kullanmak

Durum: Bir mühendislik ekibi, mikroservislerin ürettiği event'leri infinite retention süresiyle Kafka topic'lerine yazmaya karar verir ve Aggregate rehydration işlemlerini doğrudan Kafka partition'ları üzerinden yapmayı planlar.

Kritik Risk: Kafka, saniyede milyonlarca mesajı stream etmek için tasarlanmış mükemmel bir altyapıdır ancak Event Sourcing mimarisi için tam teşekküllü bir Event Store değildir. Kafka, yukarıda detaylandırılan Optimistic Concurrency Control mekanizmasına sahip değildir. Kafka'ya mesaj publish ederken "bu mesajı sadece stream'in versiyonu 5 ise partition'a yaz, aksi halde reddet" diyemezsiniz. Kafka sadece veriyi partition sonuna append eder. Bu durumda eşzamanlı gelen iki komut Kafka'ya sırasıyla yazılır, concurrency exception fırlatılamaz ve domain mantığındaki lost update problemi engellenemez.

Çözüm Mimarisi: Doğru Veritabanı Topolojisi

Event Store olarak EventStoreDB gibi bu iş için özel tasarlanmış çözümler veya PostgreSQL gibi ACID garantileri sunan ilişkisel veritabanları kullanılmalıdır. Postgres üzerinde JSONB kolonları ve unique constraint'ler (StreamId + SequenceNumber) kullanılarak çok güçlü bir Event Store tasarlanabilir. Event Store, bir system of record olarak davranıp transaction bütünlüğünü ve concurrency'yi sağlar; Kafka veya RabbitMQ ise bu veritabanından CDC ile alınan event'leri diğer servislere taşıyan bir event bus olarak görev yapar.

CQRS Projeksiyonları ve Operasyonel İzolasyon

Event Store, append-only log tabanlı yapısı gereği yüksek throughput sunar ancak üzerinde karmaşık iş sorguları, group by, sayfalama veya join işlemleri çalıştırılamaz. Son bir ayda belirli bir kategoriden alışveriş yapan premium kullanıcıların listesini bir Event Store üzerinden çekmek imkansızdır. Bu noktada Command Query Responsibility Segregation devreye girer.

Case 4: Olay Güdümlü Ağlarda Duplicate ve Out-of-Order Mesajlar

Durum: CQRS'in Query katmanındaki asenkron projection worker'ları, Event Store'dan gelen logları event bus üzerinden dinleyerek amaca özel okuma modelleri inşa eder. Metin araması için Elasticsearch, anlık sorgular için Redis, ilişkisel raporlamalar için Postgres read modelleri oluşturulur.

Kritik Risk: Dağıtık ağlarda message broker'lar exactly-once teslimatı mutlak olarak garanti edemezler. Ağ kopmaları, consumer group rebalance işlemleri veya acknowledgment timeout'ları nedeniyle bir mesaj aynı projection servisine duplicate olarak iletilebilir. Ayrıca partition key yapılandırma hataları nedeniyle event'ler out-of-order olarak ulaşabilir. Okuma modeli bir BalanceIncreased event'ini iki kere işlerse read model tamamen bozulur.

Çözüm Mimarisi: Idempotency, Checkpoint ve Dead-Letter Queue

Okuma modellerini güncelleyen projection handler'ları kesinlikle idempotent tasarlanmalıdır. Bir event'in sisteme bir kez veya bin kez uygulanması read modelin nihai state'ini değiştirmemelidir. Bu koruma, okuma veritabanında tutulan Checkpoint tabloları ile sağlanır. Projection katmanı mesajı işlerken gelen event'in sequence numarasını okur ve Checkpoint'teki son işlenen değer ile karşılaştırır. Eğer gelen değer Checkpoint değerinden büyükse okuma modelini günceller ve Checkpoint'i aynı atomik transaction içinde ileri taşır.

Eğer sistem beklenen sequence 5 iken doğrudan 6 numaralı event'i alırsa, bu out-of-order bir teslimattır. 6 numaralı event işlenmeden geçici bir in-memory buffer'ına veya dead-letter queue yapısına alınır. 5 numaralı event ağ üzerinden gecikmeli de olsa gelip işlendikten sonra buffer'daki 6 numara işleme alınarak ordering garantisi korunur.

Mikroservisler Arası Uzun Soluklu İşlemler: Saga Pattern

Event-driven mimarilerde tek bir Aggregate sınırları içindeki veri tutarlılığını Event Sourcing ve OCC ile çözebiliriz. Ancak iş kuralları birden fazla mikroservisi kapsıyorsa, dağıtık bir transaction yönetimine ihtiyaç duyulur.

Case 5: Dağıtık Transaction İhtiyacı ve Rollback Zorluğu

Durum: E-ticaret platformunda bir sipariş süreci; Sipariş, Ödeme, Envanter ve Kargo servislerini kapsar. Sipariş oluşturulur, ödeme başarıyla tahsil edilir ancak Envanter servisi stok yetersizliği nedeniyle işlemi reddeder.

Kritik Risk: Monolitik bir sistemde bu durum tüm tabloları tek bir rollback komutu ile eski haline döndürerek çözülür. Ancak dağıtık mimaride ödeme servisi kendi lokal veritabanına işlemi commit etmiş ve dış dünyadaki ödeme geçidinden parayı çekmiştir. Klasik anlamda bir veritabanı rollback işlemi artık imkansızdır.

Çözüm Mimarisi: Orchestration Tabanlı Saga ve Compensating Events

Bu uzun soluklu iş akışını yönetmek için Saga Pattern kullanılır. Saga, dağıtık bir transaction'ı her biri kendi lokal transaction'ına sahip asenkron adımlara böler. Kompleks akışlarda süreci yönetmek için bir Process Manager (veya State Machine) konumlandırılır. Process Manager, tüm sürecin güncel state'ini takip eder ve komutları ilgili servislere sırasıyla gönderir.

Eğer akışın herhangi bir adımında bir servis business rule hatası fırlatırsa (envanterin bitmesi gibi), Process Manager süreci tersine çevirmeye başlar. Önceki başarılı işlemleri iptal etmek için Compensating Event'ler fırlatır. Ödeme servisine RefundPaymentCommand komutunu gönderir. Ödeme servisi bu komutu alıp iadeyi gerçekleştirir ve PaymentRefunded event'ini fırlatır. Sistem, asenkron adımlarla geriye doğru ilerleyerek sonuçta tutarlı bir state'e (Eventual Consistency) geri döner.

Schema Evolution ve Mimari Darboğazlar

Uzun ömürlü kurumsal projelerde, iş gereksinimleri geliştikçe domain event'lerin şemaları da evrimleşmek zorundadır.

Case 6: Immutable History ve Schema Mismatch

Durum: Yıllardır çalışan sistemde iş kuralları değiştiğinde OrderCreated event'ine TaxNumber adında yeni bir zorunlu alan eklenmesi gerekir.

Kritik Risk: Event Sourcing'in temel kuralı gereği, Event Store'a yazılmış geçmiş milyonlarca olay doğrudan manipüle edilemez. Diskteki eski olaylar TaxNumber alanından yoksundur. Rehydration sırasında güncellenmiş domain modeli bu eski event'leri parse etmeye çalıştığında schema mismatch nedeniyle deserialization hataları fırlatır ve sistem çöker.

Çözüm Mimarisi: Upcasting Deseni

Bu problemi Event Store donanımına dokunmadan çözmek için Upcasting deseninden faydalanılır. Eski versiyon (V1) olaylar veritabanından memory'e okunurken araya özel bir Upcaster middleware katmanı girer. Bu katman, V1 formatındaki olayı yakalar, eksik alanları varsayılan değerler, tenant bilgileri veya business logic içerisindeki özel algoritmalarla doldurarak on-the-fly (çalışma zamanlı) olarak V2 formatına dönüştürür. Domain modeli her zaman uygulamanın en güncel versiyonu ile muhatap olur. Orijinal audit tarihçesi diskte bozulmadan korunur.

Case 7: Rehydration Maliyeti ve Ağ Yükü

Durum: Uzun ömürlü bir finansal Aggregate zaman içinde on binlerce event biriktirir.

Kritik Risk: Her yeni command geldiğinde, business rule'ları doğrulamak için bu on binlerce event'in Event Store'dan network üzerinden çekilip memory'de tek tek oynatılması gerekir. Bu durum rehydration sürelerini operasyonel olarak kabul edilemez seviyelere (saniyelere) çıkarır. Servisler üzerinde ciddi bir CPU ve I/O bottleneck'i oluşur.

Çözüm Mimarisi: Snapshotting Optimizasyonu

Performans kaybını engellemek için Snapshotting stratejisi devreye alınır. Belirli koşullar sağlandığında (örneğin her 200 event sonrasında veya scheduler yardımıyla her gece yarısı), Aggregate'in o anki memory'de hesaplanmış tam durumu ayrı bir Snapshot tablosuna JSON formatında kaydedilir. Yeni command geldiğinde sistem ilk event'ten başlamaz; en son kaydedilen Snapshot'ı referans noktası olarak memory'e yükler. Sadece o snapshot alındıktan sonraki sequence'a sahip yeni event'leri Event Store'dan çekerek state'in üzerine uygular. Rehydration süresi dramatik şekilde milisaniyelere düşürülür.

Olay Güdümlü Sistemlerde Observability

Mikroservisler arası asenkron iletişimin artması, sistemin debug edilmesini ve monitör edilmesini son derece zorlaştırır.

Case 8: Kara Kutu Problemi

Durum: Kullanıcı web arayüzünden bir işlem başlatır, API Gateway bu isteği alır, arkada mikroservisler arası onlarca event fırlatılır, ancak işlem bir noktada takılır ve kullanıcının ekranına yansımaz.

Kritik Risk: Hangi servisin event'i consume etmediğini, hangi projection'ın lag yarattığını veya hangi Saga adımında sürecin fail olduğunu geleneksel text log dosyalarına bakarak bulmak imkansızdır. Sistem operasyon ekipleri için adeta bir kara kutuya dönüşür.

Çözüm Mimarisi: Distributed Tracing ve Correlation ID

Bu problemi çözmek için OpenTelemetry gibi endüstri standardı araçlarla Distributed Tracing altyapısı kurulmalıdır. Sisteme giren ilk HTTP request'ine benzersiz bir CorrelationId atanır. Bu ID, sistem boyunca fırlatılan her command ve event'in envelope header'ına eklenir. Loglama sistemlerinde (ELK veya Jaeger gibi) bu ID aratıldığında, isteğin tüm servislerdeki yolculuğu uçtan uca görülebilir. Ek olarak, olayların birbirini nasıl tetiklediğini gösteren nedensellik bağını kurmak için CausationId kullanılır. Bir event'in CausationId verisi, onu fırlatan önceki komutun veya event'in ID'sine eşittir. Bu metadata sayesinde süreçler birbirine bağlı bir ağaç (DAG) yapısında görselleştirilebilir.

Dağıtık mimarilerde CQRS ve Event Sourcing'i bir arada kullanmak, veritabanı düzeyindeki operasyonel izolasyonu güçlü bir altyapıya dönüştürür. Sistem, otonom kararlar alabilen, kendi geçmişini asla inkar edemeyen ve audit süreçlerine doğal uyum sağlayan, veri kaybına karşı son derece dirençli bir yapıya kavuşur. Elde edilen bu muazzam esneklik, bağımsız ölçeklenme kapasitesi ve donanım kaynaklarının efektif kullanımı; sistemin sürdürülebilirliği açısından büyük avantajlar sunar. Ancak mimarinin doğası gereği ortaya çıkan bu operasyonel güç; sürekli izlenmesi gereken projection lag süreleri, karmaşık idempotency senaryoları, upcasting dönüşümleri, dağıtık transaction maliyetleri, detaylı loglama altyapıları ve operasyon ekiplerinin üstlenmesi gereken bakım eforu gibi ciddi mimari trade-off'ların bilinçli olarak yönetilmesini gerektirir.