Oyun Kitabı

Dağıtık Sistemlerde CQRS (Command Query Responsibility Segregation): Event, Broker ve Projection (Dagitik Sistemlerde CQRS Event Broker Ve Projection)

CQRS dağıtık sistemlerde nasıl çalışır? Domain event, message broker, projection, Outbox, idempotency ve eventual consistency kararlarını uçtan uca inceleyin.

Distributed CQRS event flow from command to projections and read models

Önce çözmeye çalıştığımız problem

Bir sipariş oluşturuldu. Stok servisi haberdar olmalı, bildirim servisi e-posta göndermeli, analitik sistemi rapor hazırlamalı ve arama servisi yeni siparişi indekslemelidir.

Order Service
  → Inventory için HTTP çağrısı
  → Notification için HTTP çağrısı
  → Analytics için HTTP çağrısı
  → Search için HTTP çağrısı

Peki Order Service bunların hepsini tek tek HTTP ile mi çağırmalı? Her yeni yetenek çağrı zincirini, hata yüzeyini ve birlikte deploy edilme baskısını büyütür. Hayır. Order Service yalnızca gerçekleşen iş gerçeğini paylaşmalı; onu kimin nasıl kullanacağı tüketicinin sorumluluğu olmalıdır.

CQRS'in ilk iki bölümünde bir isteğin command ve query tarafında nasıl ilerlediğini ayırdık. Dağıtık sistemde yeni soru şudur: Sipariş kabul edildikten sonra stok, bildirim, analitik ve arama ekipleri bu değişikliği nasıl güvenle öğrenir?

POST /orders
  → Command Handler
  → OrderPlaced olayı
  → Outbox
  → Relay
  → Message Broker
  ├─ Inventory projection
  ├─ Notification consumer
  ├─ Analytics projection
  └─ Search read model

Event, gerçekleşmiş ve değişmez bir iş gerçeğidir; OrderPlaced bir niyet değil, olmuş bir olgudur. Broker, event'i üretenden tüketenlere taşır. Consumer, kendi sorumluluğu için event'i işler. Projection ise event akışından okumaya uygun bir görünüm üretir.

Kavramlar ilk geçtiği yerde

📦 Domain event
Geçmiş zamanda ifade edilen, iş alanında gerçekleşmiş değişmez bir gerçektir.

📦 Broker
Mesajı kalıcılaştıran ve tüketicilere dağıtan iletişim katmanıdır.

📦 Projection
Event'lerden belirli bir ekran veya sorgu için üretilen read modeldir.

📦 Idempotency
Aynı event iki kez gelirse sonucu ikinci kez değiştirmeme özelliğidir.

Bir command PlaceOrder der; sistemden bir davranış ister. Başarılı olursa OrderPlaced yayınlanabilir. Command reddedilebilir; event ise artık değiştiremeyeceğiniz bir gerçektir.

Broker teslimatı çoğu zaman at-least-once olur: Aynı mesaj yeniden gelebilir. Consumer bu nedenle mesaj kimliğini saklayarak veya işlemi doğal olarak idempotent tasarlayarak duplicate'leri güvenle ele almalıdır.

if processedEvents.contains(event.id):
  return

applyProjection(event)
markProcessed(event.id)

Bu kontrolün maliyeti genellikle event kimliği için indeks sorgusudur: ortalama O(1) lookup veya B-tree ile O(log n). Buna karşılık duplicate bir siparişin operasyonel maliyeti çok daha büyüktür.

Tasarım kararlarının arkasındaki neden

PlaceOrder  → bir istektir.
OrderPlaced → olmuş bir gerçektir.

Bu ayrımı yaparız; çünkü command başarısız olabilir, event ise başka sistemlerin güvenle tepki verebileceği geçmişte kalmış gerçektir.

  • Outbox gerekir; çünkü broker ile veritabanı aynı ACID transaction'ı paylaşmaz.
  • Projection gerekir; çünkü admin paneli, mobil uygulama, dashboard ve analitik aynı siparişi farklı şekillerde ve aggregate'i yüklemeden okumak ister.
  • Idempotency gerekir; çünkü broker güvenilir teslimat için aynı mesajı yeniden gönderebilir.
  • Saga gerekir; çünkü e-ticarette ödeme başarısız olduğunda stok sonsuza kadar rezerve kalmamalıdır.
  • CQRS kullanan uygulamaların büyük çoğunluğu Event Sourcing kullanmaz; ikisi birbirinden bağımsız kararlardır.

Tek servis sınırı aşıldığında ne değişir?

Tek uygulamada transaction, veri kaydı ve yan etkiler aynı işlem içinde yönetilebilir. Sistem dağıldığında stok, e-posta ve raporlama kendi yaşam döngülerine sahip olur. Bir servisin başka bir servise senkron HTTP çağrısı yapması kısa vadede kolay görünür; fakat her çağrı gecikme, hata yayılımı ve birlikte deploy edilme baskısı ekler.

Event tabanlı akış bu bağımlılığı veri sözleşmesine taşır. Sipariş servisi OrderPlaced olayının şemasından sorumludur; stok servisi yalnızca ihtiyacı olan kısmı tüketir. Üretici, tüketicinin veritabanını veya çalışma anını bilmez. Bağımsız deploy edilebilirlik, bağımsız gözlemlenebilirlik olmadan yalnızca riskin yer değiştirmesidir.

Event'i transaction sınırından güvenle çıkarma

Naif akış şöyledir:

Save order
  → Commit
  → Publish OrderPlaced

Commit başarılıyken publish başarısız olursa sistem siparişi bilir, diğer servisler bilmez. Ters sırada publish edip commit'in başarısız olması da hayalet event üretir. Outbox pattern iki yazımı aynı yerel transaction'a alır:

Transaction
  → Order kaydını yaz
  → Outbox'a OrderPlaced kaydını yaz
  → Commit

Relay
  → Outbox kaydını broker'a ilet
  → teslim edildi olarak işaretle

Outbox, dağıtık transaction kurmaz; kritik gerçeği güvenceye alır: veritabanında sipariş varsa yayınlanacak event de vardır. Relay tekrar deneyebilir. Bu yüzden consumer tarafındaki idempotency gereksinimi ortadan kalkmaz.

CDC değişiklikleri veritabanı günlüğünden yakalayabilir. CDC, mevcut bir veritabanındaki değişimi yaymak için güçlüdür; ancak domain dili üzerinde kontrolünüz sınırlı olabilir. Outbox ise hangi event'in iş anlamı taşıdığını uygulamanın açıkça seçmesini sağlar.

Soru CDC Outbox
Event'i domain diliyle seçme Sınırlı Açık ve tam
Uygulama transaction'ıyla bağ Dolaylı Doğrudan
Consumer idempotency ihtiyacı Var Var

Projection: kopya değil, amaç odaklı görünüm

Read model, yazma modelinin eksik kopyası değildir. Bir ürün listesi için ProductSearchRow, operasyon paneli için OrderFulfilmentSummary üretilebilir. Aynı event, farklı ekiplerin farklı projeksiyonlarını besleyebilir.

OrderPlaced
  → OrderSummaryProjection
  → { orderId, customerName, total, status }

OrderPlaced
  → InventoryProjection
  → { sku, reservedQuantity, availability }

Projection'lar yeniden kurulabilir olmalıdır. Kod değiştiğinde veya hata düzeltildiğinde event akışı kontrollü biçimde yeniden oynatılır ve yeni read model doğrulanır. Bunun için event sırası, checkpoint, sürümleme ve replay hızının ölçülmesi gerekir. Projection gecikmesi ürün dilinde görünür olmalıdır: Kullanıcı sipariş verdikten sonra listede birkaç saniye sonra görünmesi kabul edilebilir mi? Yanıt teknik değil, iş kararıdır.

Broker seçimi bir marka tercihi değildir

Broker; sıralama, kalıcılık, consumer group, replay ve hata kuyruğu garantileri sunar. Aynı anahtar için sıra önemliyse partition key tasarlanmalıdır. Consumer geride kalabiliyorsa lag, retry ve dead-letter stratejisi izlenmelidir. Şema değişiyorsa yeni alan ekleyin, eski alanı aceleyle silmeyin; event versiyonu geriye uyumlu kalmalıdır.

Event Sourcing ile CQRS aynı şey değildir

CQRS, okuma ve yazma sorumluluklarını ayırır. Event Sourcing ise aggregate durumunu güncel satır yerine append-only event akışından üretir. Birlikte kullanılabilirler; ancak CQRS için Event Sourcing, Event Sourcing için de Kafka zorunlu değildir. Event Sourcing seçildiğinde snapshot, stream sürümü ve replay maliyeti ayrıca tasarlanır.

Saga: dağıtık iş akışındaki telafi kararı

Bir sipariş ödeme, stok ve kargo adımlarını aşabilir; bu adımlar tek ACID transaction'a sığmaz. Saga, her yerel işlem için bir telafi adımı tanımlar.

Order placed
  → reserve inventory
  → capture payment
  → create shipment

payment fails
  → release inventory
  → mark order failed

Choreography servislerin event'lerle birbirini tetiklemesini sağlar; yerel bağımlılık düşüktür ama akışın tamamını takip etmek zorlaşır. Orchestration merkezi süreç yöneticisiyle akışı görünür kılar; buna karşılık koordinasyon sorumluluğu ekler.

Operasyonel kontrol listesi

  1. Her event geçmiş zamanda ve iş diliyle mi adlandırılmış?
  2. Outbox, veritabanı kaydı ile event yayınlama arasındaki boşluğu kapatıyor mu?
  3. Consumer duplicate, sıra bozulması ve geç gelen event'lerde güvenli mi?
  4. Projection checkpoint'i, lag'i ve yeniden kurma prosedürü gözlemlenebiliyor mu?
  5. Event sözleşmesinin sahibi, sürüm stratejisi ve geriye uyumluluk kuralı açık mı?
  6. Kullanıcıya görünen eventual consistency penceresi ürün tarafından kabul edilmiş mi?

Bu soruların cevabı yoksa sistem event-driven görünür; fakat failure-driven davranır.

Sık karıştırılan ayrımlar

❌ Event = Command
✓ Command niyettir; event gerçekleşmiş gerçektir.

❌ Broker = Event Store
✓ Broker event'i taşır; Event Store domain geçmişinin kalıcı kaynağı olabilir.

❌ Projection = cache
✓ Cache hız için geçicidir; projection iş sorgusu için bilinçli bir read modeldir.

❌ At-least-once = hata
✓ Tekrar teslimat normaldir; consumer idempotent olmalıdır.

Uçtan uca gerçek akış

POST /orders
  → Controller
  → Mediator.Send()
  → Validation + Authorization
  → Transaction Behavior
  → PlaceOrderHandler
  → Order aggregate
  → Order + Outbox commit
  → Relay
  → Broker
  → Projection consumer
  → Read database
  → GET /orders
  → Query Handler
  → OrderSummaryDto
  → Frontend

Bu modeli ne zaman kullanmalıyım?

Önce mantıksal CQRS ile başlayın: command'leri iş diliyle adlandırın, query'leri ekranın ihtiyacına indirin ve transaction sınırını belirginleştirin. Broker ve ayrı projection, ancak bağımsız tüketiciler, asimetrik okuma yükü veya yeniden oynatılabilir entegrasyon ihtiyacı kanıtlandığında eklenmelidir.

Her yeni consumer'ın maliyeti O(1) kod değişikliği gibi görünse de operasyonel maliyeti sabit değildir: sözleşme, dashboard, alarm, retry, sahiplik ve test senaryosu gerekir.

Reflection

Dağıtık CQRS'in vaadi daha fazla mesaj değil, daha görünür sorumluluklardır. Event'ler geçmişi, broker akışı, projection ise kullanıcıya görünen sonucu taşır.

Bir event akışı ancak tekrar geldiğinde, geç geldiğinde ve yeniden oynatıldığında güvenliyse mimari karar haline gelir.

Bir sonraki bölümde sistem çalışırken bozulduğunda nelerin değiştiğine bakacağız: tutarlılık, duplicate'ler, bozuk projection'lar ve kurtarma stratejileri.

Bu yazıdan aklında ne kalmalı?

Yalnızca beş şeyi hatırlayacaksan:

  1. Command bir davranış ister.
  2. Event olmuş gerçektir.
  3. Broker event'i doğru tüketicilere taşır.
  4. Projection, aynı gerçeği her ekranın ihtiyacına göre okur.
  5. Outbox event kaybını önler; çünkü broker ile veritabanı aynı ACID transaction'ı paylaşmaz.

Bu bölümdeki her ayrımın bir nedeni var: idempotency gerekir çünkü broker yeniden teslim edebilir; projection gerekir çünkü aggregate'i her ekran için sorgulamak pahalı ve yanlış soyutlamadır.

SSS

Sık sorulan sorular

Domain event nedir?

Geçmiş zamanda ifade edilen, iş alanında gerçekleşmiş değişmez bir gerçektir.

Broker nedir?

Mesajı kalıcılaştıran ve tüketicilere dağıtan iletişim katmanıdır.

"Event = Command" doğru mu?

Command niyettir; event gerçekleşmiş gerçektir.

Ogrenilen Muhendislik Prensipleri

  • Dağıtık sistemde event, komut değil; gerçekleşmiş ve değişmez bir iş gerçeğidir.
  • At-least-once teslimatın karşılığı idempotent consumer'dır; duplicate mesaj istisna değil tasarım girdisidir.
  • Projection yeniden kurulabilir, ölçülebilir ve gecikmesi ürün dilinde tanımlanmış olmalıdır.

Okumaya devam et

Okumaya devam et

Ilgili yazilar

Ilgili yazilar

Ilgili yazilar

Paylaş