RabbitMQ Derinlemesine — Protokolden Prodüksiyona — Bölüm 38 — CQRS ve Event-Driven Mimaride RabbitMQ’nun Rolü
KISIM IX — Mesajlaşma Desenleri
Bir dilin harflerini bilmek bir şeydir; o harflerle şiir yazmak başka. Bu serinin ilk kısımları RabbitMQ’nun harflerini — exchange’leri, kuyrukları, mesajları — öğretti; desenler ise o harflerle nasıl anlamlı yapılar kurulacağını gösterdi. Çünkü gerçek ustalık, araçları tek tek bilmekte değil, onları bir araya getirerek dağıtık dünyanın karmaşık sorunlarına zarif çözümler örmektedir.
İçindekiler
» Desenlerden Mimariye: Olaylar Etrafında Kurmak
» Event-Driven Architecture: Olaylarla İletişim
» Olay ve Komut: Temel Ayrım
» EDA’nın Faydaları ve Bedelleri
» CQRS: Okuma ve Yazmayı Ayırmak
» CQRS Nasıl Çalışır: Olaylarla Projeksiyon
» CQRS’in Faydaları ve Bedelleri
» Event Sourcing: Olayları Gerçeğin Kaynağı Yapmak
» Event Sourcing’in Faydaları ve Bedelleri
» Üçü Birlikte: EDA, CQRS, Event Sourcing
» Canlı Oran Senaryosu: Olay-Tabanlı Bahis Sistemi
» Laboratuvar: Olay-Tabanlı Bir Akış Kurmak
» Üç Gözlem Sorusu
» Kapanış ve Kısım IX’in Sonu
Desenlerden Mimariye: Olaylar Etrafında Kurmak
↗ Bölüm 35–37'de mesajlaşma desenlerini gördük: work queue, pub/sub, RPC, outbox, saga. Bu desenlerde tekrar tekrar karşımıza bir kavram çıktı: olay (event). Outbox olayları yayımlar, saga olaylarla ilerler, pub/sub olayları dağıtır. Kısım IX’u kapatan bu bölümde, bir adım geri çekilip büyük resme bakıyoruz: tüm bir mimariyi olaylar etrafında kurmak ne demektir?
[Ayrıca bakınız: Event-Driven Architecture yazı dizisi » Bölüm 7: CQRS — Command Query Responsibility Segregation]
Bu bölüm, desenlerin doruğa ulaştığı yerdir. Tek tek desenlerden, bütünsel mimari yaklaşımlara geçiyoruz: Event-Driven Architecture (olay-tabanlı mimari), CQRS (komut-sorgu sorumluluğu ayrımı) ve Event Sourcing (olay kaynağı). Bunlar, ayrı desenler değil, sistemleri olaylar etrafında düşünmenin ve kurmanın kapsamlı yollarıdır. Onları anlamak, RabbitMQ’yu yalnızca bir araç olarak değil, bir mimari felsefenin omurgası olarak görmektir.
Neden olaylar etrafında kurmak? Çünkü olaylar, gevşek bağlı, ölçeklenebilir ve esnek sistemler kurmanın doğal yoludur. Bir olay, “bir şey oldu” der; ve ilgilenen herkes buna tepki verebilir, üretici onları bilmeden. Bu, ↗ Bölüm 35'teki pub/sub’ın ayrıştırmasını bir mimari ilkeye yükseltir: sistemler, doğrudan çağrılarla değil, olaylarla iletişir. Olaylar etrafında kurmak, esnek ve evrilebilir mimarilerin temelidir.
Bu bölümün, serinin desenlerini bir araya getirdiğini vurgulayalım. EDA, pub/sub’ı (↗ Bölüm 35) bir mimari ilkeye taşır; CQRS ve event sourcing, olayları (↗ Bölüm 36'daki outbox, ↗ Bölüm 37'deki saga) merkeze alır. Bu yaklaşımlar, öğrendiğimiz her şeyin — mesajlaşmanın, güvenilirliğin, desenlerin — bütünsel bir mimaride nasıl birleştiğini gösterir. RabbitMQ, bu mimarinin merkezindedir.
Bu geçişin bir düşünce değişimi gerektirdiğini belirtelim. Doğrudan çağrı dünyasında, “A, B’yi çağırır ve cevabını bekler” diye düşünürsünüz — senkron, buyurgan, bağlı. Olay dünyasında ise, “A bir olay yayımlar, ilgilenenler tepki verir” diye düşünürsünüz — asenkron, bildirim-tabanlı, gevşek. Bu, yalnızca teknik bir değişiklik değil, bir zihniyet değişimidir: sistemi, çağrı zincirleri olarak değil, olay akışları olarak görmek. Bu zihniyeti benimsemek, olay-tabanlı mimarileri doğru tasarlamanın ilk adımıdır.
Desenlerden mimariye geçişi bilmenin dersi şudur: tek tek desenlerin ötesinde, sistemleri olaylar etrafında kurmanın bütünsel yaklaşımları vardır — EDA, CQRS, event sourcing. Bunlar, pub/sub’ın ayrıştırmasını ve olayların merkeziliğini bir mimari felsefeye yükseltir; ve RabbitMQ onların omurgasıdır. Bu bölüm, serinin desenlerinin doruğa ulaştığı yerdir. İlk yaklaşımla — Event-Driven Architecture ile — başlayalım.
Event-Driven Architecture: Olaylarla İletişim
İlk ve en kapsayıcı yaklaşım, Event-Driven Architecture’dır (EDA — olay-tabanlı mimari): sistemlerin olaylar aracılığıyla iletişmesi. Bu mimariyi anlamak, bileşenleri doğrudan çağrılar yerine olaylarla birbirine bağlamanın felsefesini kavramaktır; çünkü EDA, gevşek bağlılığı bir mimari ilke haline getirir.
[Ayrıca bakınız: Event-Driven Architecture yazı dizisi » Giriş]
EDA’nın fikrini görelim. Geleneksel bir mimaride, bileşenler birbirini doğrudan çağırır: A servisi, B servisini çağırır. EDA’da ise, bileşenler olaylar üretir ve olaylara tepki verir: A servisi bir olay yayımlar (“bir şey oldu”) ve ilgilenen bileşenler buna tepki verir. A, kimin tepki verdiğini bilmez; yalnızca olayı yayımlar. Bileşenler, doğrudan değil, olaylar aracılığıyla iletişir. Sistem, olayların akışıyla çalışır.
EDA’nın merkezinde, olayın birinci sınıf bir vatandaş olması yatar. Bir olay, olmuş bir şeyin kaydıdır — bir gerçektir (fact). “Bahis kabul edildi”, “oran güncellendi”, “ödeme alındı” — bunlar olaylardır. Bileşenler olayları yayımlar; ilgilenen bileşenler dinler ve tepki verir. Bu, ↗ Bölüm 35'teki pub/sub’ın mimari ölçeğe taşınmış halidir: tüm sistem, olayları yayınlayan ve dinleyen bileşenlerden oluşur. Aşağıdaki diyagram bu mimariyi gösterir.
Olayların iki türünden de söz edelim. Alan olayları (domain events), bir servisin kendi içinde anlamlı olaylardır (örneğin “bahis kuralı uygulandı”). Entegrasyon olayları (integration events) ise, servisler arasında paylaşılan, mimari düzeyde olaylardır (örneğin “bahis kabul edildi”). EDA’da, özellikle entegrasyon olayları önemlidir: bunlar, servisleri birbirine bağlayan, mimari ölçekteki olaylardır. Bu ayrım, hangi olayların bir servisin içinde kaldığını, hangilerinin dışarı yayıldığını netleştirmeye yardım eder.
EDA’da RabbitMQ’nun rolünü vurgulayalım: RabbitMQ, olay veriyoludur (event bus). Bileşenler olayları RabbitMQ’ya yayımlar ve RabbitMQ onları ilgilenen bileşenlere ulaştırır. ↗ bu serinin tüm öğrendikleri — exchange’ler, kuyruklar, pub/sub, güvenilirlik — bu olay veriyolunu mümkün kılar. RabbitMQ, EDA’nın iletişim omurgasıdır; olaylar onun üzerinden akar.
Event-Driven Architecture’ı bilmenin dersi şudur: EDA, sistemlerin doğrudan çağrılar yerine olaylar aracılığıyla iletişmesidir; olay, olmuş bir şeyin kaydı olarak birinci sınıf bir vatandaştır. Bileşenler olayları yayımlar ve dinler, birbirini doğrudan bilmeden. RabbitMQ, bu mimarinin olay veriyoludur. Şimdi, EDA’nın merkezindeki önemli bir ayrıma — olay ve komut ayrımına — bakalım.
Olay ve Komut: Temel Ayrım
EDA’da iki tür mesaj vardır ve aralarındaki ayrım temeldir: olay (event) ve komut (command). Bu ayrımı anlamak, olay-tabanlı bir sistemi doğru tasarlamanın anahtarıdır; çünkü olaylar ve komutlar farklı anlamlar taşır ve farklı biçimde kullanılır.
Farkı görelim. Bir komut, bir şeyin yapılmasını isteyen bir mesajdır — buyurgan (imperative) ve tek bir alıcıya yöneliktir. “Bahsi yerleştir”, “ödemeyi al” — bunlar komutlardır. Bir komut, belirli bir işleyiciye (handler) gönderilir ve o işleyicinin bir eylem yapmasını bekler. Bir olay ise, bir şeyin olduğunu bildiren bir mesajdır — bir gerçektir (fact) ve yayınlanır. “Bahis yerleştirildi”, “ödeme alındı” — bunlar olaylardır. Bir olay, kimseye bir şey yapmasını emretmez; yalnızca olmuş bir şeyi duyurur, ve ilgilenen herkes ona tepki verebilir.
Bu farkın yönelim açısından önemini vurgulayalım. Komut tek bir alıcıya yöneliktir (bir şey yap); olay ise çok alıcıya yayınlanabilir (bir şey oldu, ilgilenen tepki versin). Komut, ↗ Bölüm 35'teki work queue/yönlendirme ile tek bir işleyiciye gider; olay, pub/sub ile birçok dinleyiciye yayılır. Komut bir istek, olay bir bildirimdir. Bu, isimlendirmede de görünür: komutlar emir kipinde (“yap”), olaylar geçmiş zamanda (“oldu”) adlandırılır. Aşağıdaki tablo bu farkı özetler.
Boyut Komut Olay Anlamı Bir şey yap (istek) Bir şey oldu (gerçek) Yönelim Tek alıcı Çok alıcı (yayın) Kip Emir (“bahsi yerleştir”) Geçmiş (“bahis yerleştirildi”) Desen Yönlendirme / work queue Pub/sub Beklenti Bir eylem Tepki (isteğe bağlı)
Bu ayrımın neden önemli olduğunu vurgulayalım. Olay ve komutu karıştırmak, mimaride kafa karışıklığı yaratır. Bir komutu olay gibi yayınlamak (birçok alıcının aynı işi yapması), ya da bir olayı komut gibi tek bir alıcıya yöneltmek (ayrıştırmayı kaybetmek), yanlış davranışlara yol açar. Doğru ayrımı yapmak — “bu bir istek mi, yoksa bir bildirim mi?” — olay-tabanlı bir sistemin temelini doğru kurmaktır.
Olayların iki kullanım biçiminden de söz edelim. Bir olay, yalnızca “bir şey oldu” diye bildirebilir (bildirim — notification); tüketici, ayrıntı için geri sormak zorunda kalabilir. Ya da olay, ilgili veriyi de taşıyabilir (olay-taşımalı durum transferi — event-carried state transfer); tüketici, ihtiyacı olan veriyi olayın içinde bulur ve geri sormaz. İkincisi, ayrıştırmayı artırır (tüketici üreticiye geri bağımlı olmaz) ama olayları büyütür. Bu seçim, olayların ne kadar veri taşıyacağını belirleyen önemli bir tasarım kararıdır.
Olay ve komut ayrımını bilmenin dersi şudur: komut bir şeyin yapılmasını isteyen, tek alıcıya yönelik bir mesajdır; olay ise bir şeyin olduğunu bildiren, çok alıcıya yayınlanan bir gerçektir. Komutlar emir kipinde ve yönlendirilir, olaylar geçmiş zamanda ve yayınlanır. Bu ayrımı doğru yapmak, olay-tabanlı bir sistemi doğru tasarlamanın temelidir. Şimdi, EDA’nın getirdiği fayda ve bedellere bakalım.
EDA’nın Faydaları ve Bedelleri
EDA güçlü bir mimari yaklaşımdır, ama her güçlü yaklaşım gibi, hem faydaları hem bedelleri vardır. Bu dengeyi bilmek, EDA’yı ne zaman ve nasıl kullanacağınızı doğru kararlaştırmanın temelidir; çünkü olaylar etrafında kurmak, belirli avantajları belirli maliyetlerle getirir.
Faydalarını görelim. Gevşek bağlılık (loose coupling): üreticiler tüketicileri bilmez; bileşenler olaylar üzerinden bağımsızdır. Genişletilebilirlik (extensibility): yeni bir tüketici eklemek, üreticileri hiç değiştirmeden, yalnızca yeni bir dinleyici eklemekle olur — sistem, olay yayınlamaya devam eder, yeni bileşen dinlemeye başlar. Ölçeklenebilirlik: bileşenler bağımsız ölçeklenir; olay veriyolu yükü ayrıştırır. Dayanıklılık (resilience): bir tüketicinin sorunu, üreticiyi ya da diğerlerini etkilemez (↗ Bölüm 35'teki bağımsız kuyruklar). Bu faydalar, EDA’yı esnek ve evrilebilir sistemler için çekici kılar.
Bedellerini de görelim. Nihai tutarlılık: ↗ Bölüm 36 ve 37'de gördüğümüz gibi, olaylar asenkron aktığından, sistem nihai tutarlıdır — anlık değil. Karmaşıklık: olay akışlarını, asenkron davranışı ve nihai tutarlılığı yönetmek, doğrudan çağrılardan daha karmaşıktır. İzlenebilirlik zorluğu: bir isteğin sistemde nasıl aktığını izlemek, olaylar dağıldıkça zorlaşır (dağıtık izleme gerekir). Hata ayıklama zorluğu: asenkron, olay-güdümlü bir akışta bir sorunu izlemek, senkron bir çağrı zincirinden daha zordur. Aşağıdaki tablo bu dengeyi özetler.
EDA’nın faydaları EDA’nın bedelleri Gevşek bağlılık Nihai tutarlılık Genişletilebilirlik (yeni tüketici kolay) Karmaşıklık (asenkron akış) Bağımsız ölçeklenebilirlik İzlenebilirlik zorluğu Dayanıklılık (yalıtım) Hata ayıklama zorluğu
İzlenebilirlik bedelini biraz açalım. Senkron bir çağrı zincirinde, bir isteğin yolunu izlemek görece kolaydır: A çağırdı B’yi, B çağırdı C’yi. Ama olay-tabanlı bir sistemde, bir iş akışı birçok asenkron olaya yayılır; ve “bu sonuç hangi olaylardan doğdu?” sorusunu yanıtlamak zordur. Bu yüzden, EDA sistemleri dağıtık izleme (distributed tracing) gerektirir: olaylara bir korelasyon kimliği (correlation id) ekleyerek, bir iş akışının tüm olaylarını ilişkilendirmek. ↗ Bölüm 31'deki izlemenin, olay-tabanlı sistemlerde özel bir önemi vardır.
EDA’yı ne zaman kullanacağımızı netleştirelim. EDA, gevşek bağlılık, genişletilebilirlik ve ölçeklenebilirlik gerektiren — özellikle çok bileşenli, evrilen sistemler için — güçlü bir seçimdir. Ama getirdiği nihai tutarlılık ve karmaşıklık kabul edilebilir olmalıdır; basit, küçük sistemler için EDA gereksiz bir karmaşıklık olabilir. Çoğu olgun, ölçekli sistem için ise, EDA’nın faydaları bedellerine değer.
EDA’nın faydalarını ve bedellerini bilmenin dersi şudur: EDA gevşek bağlılık, genişletilebilirlik, ölçeklenebilirlik ve dayanıklılık sağlar; ama nihai tutarlılık, karmaşıklık ve izlenebilirlik/hata ayıklama zorluğu bedeliyle gelir. Çok bileşenli, evrilen sistemler için faydaları değerlidir; basit sistemler için gereksiz olabilir. Şimdi, olaylar üzerine kurulan bir başka güçlü desene — CQRS’e — bakalım.
CQRS: Okuma ve Yazmayı Ayırmak
Olaylar üzerine kurulan ikinci kapsamlı yaklaşım, CQRS’tir (Command Query Responsibility Segregation — komut-sorgu sorumluluğu ayrımı): yazma modelini okuma modelinden ayırmak. Bu deseni anlamak, okuma ve yazmanın farklı ihtiyaçlarını ayrı ayrı optimize etmenin gücünü kavramaktır; çünkü CQRS, “tek bir model her şeyi yapsın” varsayımını sorgular.
CQRS’in fikrini görelim. Geleneksel bir sistemde, tek bir model hem yazma (durumu değiştiren işlemler) hem okuma (durumu okuyan sorgular) için kullanılır. CQRS ise, bu ikisini ayırır: yazma modeli (write model) komutları işler ve durumu değiştirir; okuma modeli (read model) sorguları yanıtlar ve durumu okur. İki ayrı model, farklı biçimde optimize edilir. Komut tarafı ve sorgu tarafı, ayrı sorumluluklara sahiptir — adın söylediği gibi, “sorumluluk ayrımı”.
Neden ayırmak? Çünkü okuma ve yazmanın farklı ihtiyaçları vardır. Yazma, tutarlılık ve iş kurallarına odaklanır; okuma ise hız ve uygun veri şekline. Bir sistemde okuma sayısı, yazma sayısından çok daha fazla olabilir (çoğu sistemde okumalar baskındır); ve okumalar, yazma modelinin yapısına uymayan biçimlerde gerekebilir. Tek bir model, ikisini birden iyi yapmaya çalışırken, ikisinde de orta kalır. Ayırmak, her birini kendi ihtiyacına göre optimize etmeyi mümkün kılar. Aşağıdaki diyagram bu ayrımı gösterir.
CQRS’in olaylarla nasıl birleştiğini belirtelim: yazma modeli durumu değiştirir ve olaylar yayımlar; bu olaylar, okuma modelini günceller. Yani okuma modeli, olaylardan inşa edilen bir projeksiyon (projection) olur — ↗ bir sonraki başlıkta derinleşeceğiz. Bu, CQRS’i EDA ve olaylarla doğal biçimde bütünleştirir: olaylar, yazma ve okuma modellerini birbirine bağlayan köprüdür. RabbitMQ, bu olayları taşır.
CQRS’i bilmenin dersi şudur: CQRS, yazma modelini (komutlar, durumu değiştirir) okuma modelinden (sorgular, durumu okur) ayırır; böylece her biri kendi ihtiyacına göre optimize edilir. Okuma ve yazmanın farklı ihtiyaçları (tutarlılık vs hız, farklı şekiller) bu ayrımı değerli kılar; ve olaylar, iki modeli birbirine bağlar. Şimdi, CQRS’in tam olarak nasıl çalıştığına — olaylarla projeksiyona — bakalım.
CQRS Nasıl Çalışır: Olaylarla Projeksiyon
CQRS’in kalbinde, projeksiyon (projection) kavramı yatar: okuma modelinin, olaylardan inşa edilmesi. Bu mekanizmayı anlamak, CQRS’in pratikte nasıl çalıştığını kavramaktır; çünkü projeksiyon, yazma ve okuma modellerini olaylar üzerinden birbirine bağlayan köprüdür.
Akışı adım adım görelim. Bir komut geldiğinde, yazma modeli onu işler: iş kurallarını uygular, durumu değiştirir ve bir olay yayımlar. Olay, RabbitMQ üzerinden yayınlanır. Bir ya da daha çok okuma modeli (projeksiyon), bu olayı tüketir ve kendi optimize edilmiş veri deposunu günceller. Sorgular ise, bu okuma modelinden okur — hızlı ve sorgulara uygun biçimde. Yani veri, yazma modelinden olaylar aracılığıyla okuma modeline akar; okuma modeli, olayları “yansıtarak” (projecting) güncel kalır.
Projeksiyonun esnekliğini vurgulayalım. Okuma modeli, olaylardan inşa edilen bir görünümdür; ve istediğiniz biçimde olabilir: denormalize (sorgular için düzleştirilmiş), belirli sorgulara optimize, farklı bir depoda (hızlı bir önbellek, bir arama indeksi). Dahası, aynı olaylardan birden çok projeksiyon oluşturabilirsiniz; her biri farklı bir sorgu ihtiyacına optimize. Örneğin, oran olaylarından bir “oran gösterimi” projeksiyonu, bir “bahis geçmişi” projeksiyonu ve bir “analitik” projeksiyonu — hepsi aynı olaylardan, ama farklı şekillerde. Aşağıdaki diyagram bu akışı gösterir.
Projeksiyonların yeniden inşa edilebilir olduğunu vurgulayalım. Bir projeksiyon, olaylardan inşa edildiğinden, gerektiğinde sıfırdan yeniden inşa edilebilir: olayları baştan yeniden işleyerek. Bu güçlüdür: bir projeksiyon bozulursa, onu yeniden inşa edersiniz; yeni bir okuma modeli gerekirse, mevcut olaylardan onu oluşturursunuz; bir projeksiyonun şeklini değiştirmek isterseniz, yeni şekille yeniden inşa edersiniz. Okuma modelleri, böylece esnek ve onarılabilir olur — çünkü onlar gerçeğin kaynağı değil, olaylardan türetilen görünümlerdir.
Nihai tutarlılığın burada da geçerli olduğunu belirtelim. Okuma modeli, yazma modelini biraz gecikmeyle takip eder: bir olay yayımlandıktan sonra, projeksiyonun güncellenmesi zaman alır. Yani okuma modeli, yazma modelinden anlık değil, nihai olarak tutarlıdır (↗ Bölüm 36 ve 37'deki nihai tutarlılık). Bu, CQRS’in kabul ettiği bir ödünleşimdir: bir sorgu, en son yazmayı henüz yansıtmamış bir okuma modelinden okuyabilir. Uygulamanın bu gecikmeyi tolere etmesi gerekir.
CQRS’in nasıl çalıştığını bilmenin dersi şudur: CQRS, yazma modelinin olaylar yayımlamasıyla ve okuma modellerinin (projeksiyonların) bu olayları tüketip güncellenmesiyle çalışır. Okuma modeli, olaylardan inşa edilen bir projeksiyondur; ve aynı olaylardan farklı ihtiyaçlara optimize birden çok projeksiyon oluşturabilirsiniz. Okuma modeli, yazma modelinden nihai olarak tutarlıdır. Şimdi, CQRS’in fayda ve bedellerine bakalım.
CQRS’in Faydaları ve Bedelleri
CQRS güçlü bir desendir, ama her güçlü desen gibi belirli bedellerle gelir. Bu dengeyi bilmek, CQRS’i ne zaman kullanacağınızı doğru kararlaştırmanın temelidir; çünkü okuma ve yazmayı ayırmak, belirli kazançları belirli maliyetlerle getirir.
Faydalarını görelim. Bağımsız optimizasyon: yazma modeli tutarlılık ve iş kuralları için, okuma modelleri sorgu hızı ve şekli için ayrı ayrı optimize edilir. Bağımsız ölçeklenebilirlik: okuma ve yazma ayrı ölçeklenir; çoğu sistemde okumalar baskın olduğundan, okuma tarafını bağımsız ölçeklemek değerlidir. Çoklu okuma modeli: farklı sorgu ihtiyaçları için farklı projeksiyonlar. Esneklik: yeni bir okuma modeli eklemek, yazma modeline dokunmadan, yalnızca yeni bir projeksiyon eklemekle olur — olaylar zaten oradadır.
Get Sahin Yelkenci’s stories in your inbox
Join Medium for free to get updates from this writer.
Bedellerini de görelim. İki model: yazma ve okuma modellerini ayrı ayrı kurmak ve sürdürmek, daha fazla iştir. Nihai tutarlılık: ↗ bu bölümde gördüğümüz gibi, okuma modeli yazma modelini gecikmeyle takip eder; anlık tutarlılık yoktur. Karmaşıklık: projeksiyon mekanizması, olay akışı, iki modelin senkronizasyonu — hepsi karmaşıklık ekler. Aşağıdaki tablo bu dengeyi özetler.
CQRS’in faydaları CQRS’in bedelleri Bağımsız optimizasyon (yaz/oku) İki model (daha fazla iş) Bağımsız ölçeklenebilirlik Nihai tutarlılık (okuma gecikir) Çoklu okuma modeli Karmaşıklık (projeksiyon, senkron) Esneklik (yeni projeksiyon kolay) —
Nihai tutarlılığı ele almanın pratik yollarından söz edelim. Okuma modeli yazma modelini gecikmeyle takip ettiğinden, bir kullanıcı bir yazma yapıp hemen okuduğunda, eski veriyi görebilir. Bunu yönetmek için çeşitli teknikler vardır: kullanıcıya “işleniyor” durumunu göstermek, kendi yazmasını arayüzde anında yansıtmak (okuma modeli güncellenene kadar), ya da kritik durumlarda yazma modelinden okumak. Bu teknikler, nihai tutarlılığın kullanıcı deneyimine etkisini yumuşatır; CQRS’i pratikte kullanılabilir kılar.
CQRS’i ne zaman kullanacağımızı netleştirelim. CQRS, okuma ve yazma ihtiyaçları belirgin biçimde farklı olduğunda, okuma/yazma çok farklı ölçeklendiğinde ya da birden çok okuma görünümüne ihtiyaç duyulduğunda değerlidir. Ama basit bir CRUD sistemi için (okuma ve yazma benzer ve basitse), CQRS gereğinden fazla karmaşıklıktır — “altın çekiçle ceviz kırmak”. Doğru yerde güçlü, yanlış yerde gereksiz bir desendir.
CQRS’in faydalarını ve bedellerini bilmenin dersi şudur: CQRS bağımsız optimizasyon, bağımsız ölçeklenebilirlik, çoklu okuma modeli ve esneklik sağlar; ama iki model, nihai tutarlılık ve karmaşıklık bedeliyle gelir. Okuma ve yazma ihtiyaçları belirgin biçimde farklıysa değerlidir; basit CRUD için ise gereksizdir. Şimdi, olayları daha da merkeze alan bir yaklaşıma — event sourcing’e — bakalım.
Event Sourcing: Olayları Gerçeğin Kaynağı Yapmak
Olaylar üzerine kurulan en radikal yaklaşım, event sourcing’tir (olay kaynağı): güncel durumu saklamak yerine, ona yol açan olayların dizisini saklamak. Bu yaklaşımı anlamak, “gerçeğin kaynağı”nı durumdan olaylara taşımanın gücünü kavramaktır; çünkü event sourcing, verinin doğasına dair temel bir varsayımı tersine çevirir.
[Ayrıca bakınız: Event-Driven Architecture yazı dizisi » Bölüm 6: Event Sourcing — State Yerine Event Saklamak]
Event sourcing’in fikrini görelim. Geleneksel bir sistemde, güncel durumu saklarsınız ve değiştikçe üzerine yazarsınız (örneğin, “bakiye = 100” satırını güncellersiniz). Event sourcing’de ise, durumu değil, ona yol açan olayların dizisini saklarsınız (örneğin, “50 yatırıldı”, “70 yatırıldı”, “20 çekildi”). Güncel durum, bu olayları yeniden oynatarak (replay) türetilir: 50 + 70–20 = 100. Olay günlüğü (event log / event store), gerçeğin kaynağıdır; durum ise ondan türetilen bir projeksiyondur.
Bu tersine çevirmenin önemini vurgulayalım. Geleneksel sistemde, geçmiş kaybolur: bakiyeyi 100'den 80'e güncellediğinizde, eski değer ve değişimin nedeni silinir; yalnızca güncel durum kalır. Event sourcing’de ise, hiçbir şey silinmez: her değişiklik bir olay olarak eklenir (append-only — yalnızca ekleme), ve tüm geçmiş korunur. Güncel durum, bu olayların toplamıdır; ama olaylar, kendi başlarına, eksiksiz bir tarihtir. Aşağıdaki diyagram bu farkı gösterir.
Bir pratik optimizasyondan söz edelim: anlık görüntüler (snapshots). Eğer bir varlığın binlerce olayı varsa, durumu her seferinde baştan yeniden oynatmak yavaş olabilir. Çözüm, periyodik anlık görüntüler almaktır: belirli aralıklarla, o ana kadarki durumu kaydedersiniz; sonra durumu türetmek için, son anlık görüntüden başlayıp yalnızca sonraki olayları oynatırsınız. Anlık görüntüler, event sourcing’in yeniden oynatma maliyetini yönetilebilir kılan yaygın bir tekniktir; tam geçmişi korurken, türetmeyi hızlandırır.
Event sourcing’in CQRS ve RabbitMQ ile ilişkisini belirtelim. Event sourcing, çoğu zaman CQRS ile birleşir: olaylar yazma modelidir (olay deposu), okuma modelleri ise olaylardan inşa edilen projeksiyonlardır. Ve ↗ Bölüm 22'deki RabbitMQ Streams, bir olay günlüğü için doğal bir araçtır: ekleme-tabanlı (append-only), yeniden oynatılabilir (replay), uzun süre saklanan. Stream’ler, event sourcing’in olay deposu ihtiyacına çok uygundur. RabbitMQ, böylece event sourcing’in de altyapısı olabilir.
Event sourcing’i bilmenin dersi şudur: event sourcing, güncel durumu saklamak yerine, ona yol açan olayların dizisini saklar; durum, olayları yeniden oynatarak türetilir. Olay günlüğü gerçeğin kaynağıdır ve hiçbir şey silinmez (ekleme-tabanlı tam geçmiş). Çoğu zaman CQRS ile birleşir ve RabbitMQ Streams bir olay deposu olarak hizmet eder. Şimdi, event sourcing’in fayda ve bedellerine bakalım.
Event Sourcing’in Faydaları ve Bedelleri
Event sourcing, güçlü yetenekler sunar ama önemli bir karmaşıklık getirir. Bu dengeyi bilmek, event sourcing’i ne zaman kullanacağınızı doğru kararlaştırmanın temelidir; çünkü olayları gerçeğin kaynağı yapmak, hem büyük kazançlar hem ciddi maliyetler getirir.
Faydalarını görelim. Tam denetim izi (audit trail): her değişiklik bir olay olduğundan, ne olduğunun eksiksiz bir geçmişine sahip olursunuz — finansal sistemler için paha biçilmez. Yeniden oynatma ve yeniden inşa (replay/rebuild): olayları yeniden oynatarak durumu (ya da yeni bir projeksiyon) yeniden inşa edebilirsiniz; bu, yeni okuma modelleri oluşturmak, hata ayıklamak ve kurtarmak için güçlüdür. Zamansal sorgular (temporal queries): “T anında durum neydi?” sorusunu, olayları T’ye kadar oynatarak yanıtlayabilirsiniz. Olaylar gerçektir: gerçeğin kaynağı, değişebilen bir durum değil, değişmez olaylardır.
Bedellerini de görelim. Karmaşıklık: durumu saklamaktan çok daha karmaşıktır — olay deposu, yeniden oynatma mantığı, projeksiyonlar. Güncel durumu sorgulamak: güncel durumu almak, yeniden oynatma ya da sürekli güncellenen projeksiyonlar gerektirir (doğrudan bir satır okumak yerine). Olay sürümleme: olaylar değişmez bir geçmiştir; ama olay yapısı zamanla değişirse (yeni alanlar, değişen anlamlar), eski olaylarla uyumluluğu yönetmek zordur. Her zaman gerekmez: çoğu sistem, tam olay geçmişine ihtiyaç duymaz. Aşağıdaki tablo bu dengeyi özetler.
Event Sourcing’in faydaları Event Sourcing’in bedelleri Tam denetim izi Karmaşıklık (olay deposu, replay) Yeniden oynatma / yeniden inşa Güncel durum için replay/projeksiyon Zamansal sorgular Olay sürümleme zorluğu Olaylar gerçeğin kaynağı Her zaman gerekmez
Event sourcing’i ne zaman kullanacağımızı netleştirelim. Event sourcing, denetim, yeniden oynatma ya da zamansal sorgular değerli olduğunda — özellikle finansal sistemlerde, denetimin kritik olduğu yerlerde — güçlüdür. Ama getirdiği karmaşıklık ciddidir; bu yüzden, bu yeteneklere gerçekten ihtiyaç duyulduğunda kullanılmalıdır. Basit durumlar için, geleneksel durum saklama çok daha basit ve yeterlidir. Event sourcing, güçlü ama seçici bir araçtır.
Event sourcing’in faydalarını ve bedellerini bilmenin dersi şudur: event sourcing, tam denetim izi, yeniden oynatma/inşa ve zamansal sorgular sağlar; ama karmaşıklık, güncel durumu sorgulama zorluğu ve olay sürümleme bedeliyle gelir. Denetim ve yeniden oynatma değerli olduğunda güçlüdür; basit durumlar için gereğinden fazladır. Şimdi, bu üç yaklaşımın nasıl birlikte çalıştığına bakalım.
Üçü Birlikte: EDA, CQRS, Event Sourcing
EDA, CQRS ve event sourcing’i ayrı ayrı gördük; şimdi üçünün nasıl ilişkili olduğunu ve birlikte nasıl çalıştığını netleştirelim. Bu ilişkiyi anlamak, bu yaklaşımların ayrı seçenekler değil, katmanlı ve birleştirilebilir bir bütün olduğunu görmektir; çünkü üçü, ortak bir iplikle — olaylarla — bağlıdır.
Üçünün ilişkisini görelim. EDA, geniş bir mimari tarzdır: bileşenler olaylarla iletişir. CQRS, EDA içinde bir desendir: okuma ve yazmayı ayırır, olaylarla bağlar. Event sourcing, bir saklama biçimidir: olayları gerçeğin kaynağı yapar, durumu türetir. Üçü, katmanlıdır ve birleştirilebilir: yalnızca EDA kullanabilir; EDA + CQRS kullanabilir; ya da EDA + CQRS + event sourcing (tam yığın) kullanabilirsiniz. Hepsinin ortak ipliği, olaylardır: EDA’da iletişim aracı, CQRS’te modelleri bağlayan köprü, event sourcing’de gerçeğin kaynağı.
Bunun bir aşamalandırma (progression) olduğunu vurgulayalım. Olaylar, her aşamada daha merkezi hale gelir: EDA’da olaylar iletişim aracıdır; CQRS’te olaylar okuma ve yazma modellerini senkronize eder; event sourcing’de olaylar gerçeğin ta kendisidir. Her adım, olayları daha da merkeze alır. Ve bu, hep-ya-hiç değildir: ihtiyacınıza uyan seviyeyi seçersiniz. Ayrıştırma için EDA; okuma/yazma farklıysa CQRS ekleyin; denetim/yeniden oynatma önemliyse event sourcing ekleyin. Aşağıdaki diyagram bu katmanlamayı gösterir.
Üçünü de kullanmak zorunda olmadığınızı bir kez daha vurgulayalım. Yaygın bir hata, “modern” olmak için en karmaşık yaklaşımı (tam event sourcing + CQRS) gereksiz yere benimsemektir. Oysa doğru yol, basit başlayıp ihtiyaç doğdukça eklemektir: önce EDA ile ayrıştır; okuma/yazma gerçekten farklılaşınca CQRS ekle; denetim/yeniden oynatma gerçekten gerekince event sourcing’e geç. Her katman, somut bir ihtiyacı karşılamalı, bir moda uymak için değil. Gereksiz karmaşıklık, gerçek bir maliyettir.
RabbitMQ’nun üçü boyunca omurga olduğunu vurgulayalım. EDA’da olay veriyolu, CQRS’te modelleri bağlayan olay taşıyıcı, event sourcing’de olay deposu (RabbitMQ Streams) — RabbitMQ, her katmanda olayları taşır. Bu seri boyunca öğrendiğimiz her şey — exchange’ler, kuyruklar, pub/sub, streams, güvenilirlik — bu olay-tabanlı mimarileri mümkün kılar. Olaylar etrafında kurmak, RabbitMQ’yu bir araçtan bir mimari omurgaya dönüştürür.
Üçünü birlikte bilmenin dersi şudur: EDA (iletişim tarzı), CQRS (okuma/yazma ayrımı) ve event sourcing (olay-tabanlı saklama), ortak ipliği olaylar olan, katmanlı ve birleştirilebilir yaklaşımlardır. İhtiyacınıza uyan seviyeyi seçersiniz; olaylar her aşamada daha merkezi olur. RabbitMQ, üçü boyunca omurgadır. Şimdi, tüm bunları canlı oran senaryomuzda — tam bir olay-tabanlı bahis sisteminde — birleştirelim.
Canlı Oran Senaryosu: Olay-Tabanlı Bahis Sistemi
Üç yaklaşımı, canlı bahis sistemimizi tam bir olay-tabanlı mimari olarak modelleyerek birleştirelim. Bu senaryo, yalnızca bu bölümün değil, neredeyse tüm serinin bir sentezidir; çünkü olgun bir bahis sistemi, öğrendiğimiz her şeyi olaylar etrafında bir araya getirir.
Mimariyi katman katman kuralım. EDA: bahisler, oranlar, ödemeler — hepsi olaylar olarak akar. Servisler (risk, bildirim, analitik) olaylara tepki verir, birbirini doğrudan bilmeden. Bu, gevşek bağlı, genişletilebilir bir sistemdir: yeni bir servis (örneğin bir dolandırıcılık tespiti), mevcut olayları dinleyerek, hiçbir şeyi değiştirmeden eklenir. CQRS: yazma modeli (bahisleri yerleştir, işle) okuma modellerinden (oran gösterimi, bahis geçmişi, lider tablosu) ayrıdır; her okuma modeli, olaylardan bir projeksiyondur. Oran gösterimi hızlı okumaya, bahis geçmişi sorgulamaya optimize. Aşağıdaki diyagram bu mimariyi gösterir.
Event Sourcing: bahis ve oran olaylarının günlüğü (↗ Bölüm 22'deki RabbitMQ Streams), gerçeğin kaynağıdır. Bu, tam denetim (her bahis ve oran değişimi kayıtlı), yeni projeksiyonları yeniden oynatmayla inşa etme ve zamansal sorgular (“bahsin yapıldığı anda oran neydi?”) sağlar — finansal ve düzenleyici açıdan kritik. Ve tüm bunlar boyunca, RabbitMQ omurgadır: olaylar için pub/sub, olay günlüğü için stream’ler, ve tüm güvenilirlik (↗ Bölüm 36'daki outbox, ↗ Bölüm 10'daki confirm, ↗ Bölüm 15'teki idempotency).
Bu senaryonun dersi şudur: Olgun bir bahis sistemi, EDA (olaylarla gevşek bağlı iletişim), CQRS (okuma/yazma ayrımı, çoklu projeksiyon) ve event sourcing (olay günlüğü gerçeğin kaynağı) ile, tam bir olay-tabanlı mimari olarak kurulur. RabbitMQ, bu mimarinin omurgasıdır ve serinin tüm öğrendiklerini bir araya getirir. Sonuç: esnek, ölçeklenebilir ve denetlenebilir bir sistem. Şimdi, bunu kendi elimizle denemek için laboratuvara geçelim.
Laboratuvar: Olay-Tabanlı Bir Akış Kurmak
Olay-tabanlı mimariyi gerçekten kavramak, kendi elinizle küçük bir olay-tabanlı akış kurup EDA, CQRS ve event sourcing’i görmekle olur. ↗ Giriş bölümündeki laboratuvarda, bunu deneyelim. Temel fikir, olayların iletişim, senkronizasyon ve gerçeğin kaynağı olarak nasıl çalıştığını somut görmektir.
İlk deney, EDA’dır. Küçük bir olay-tabanlı akış kurun: bir üretici olaylar yayımlar, birden çok tüketici bunlara tepki verir (↗ Bölüm 35'teki pub/sub). Üreticinin tüketicileri bilmediğini ve yeni bir tüketici eklemenin üreticiyi değiştirmediğini gözlemleyin.
İkinci deney, CQRS’tir. Bir yazma modeli olaylar yayımlasın; bir okuma modeli (projeksiyon) bu olayları tüketsin ve sorguları yanıtlasın. Okuma modelinin, yazma modelini biraz gecikmeyle takip ettiğini — nihai tutarlılığı — gözlemleyin.
Üçüncü deney, event sourcing’tir. Olayları bir stream’de saklayın (↗ Bölüm 22'deki RabbitMQ Streams); durumu olayları yeniden oynatarak inşa edin. Sonra, mevcut olay geçmişini baştan yeniden oynatarak yeni bir projeksiyon ekleyin; var olan olaylardan yepyeni bir okuma modeli inşa etmenin gücünü — yeniden oynatmanın değerini — gözlemleyin. Bu, event sourcing’in en güçlü yeteneklerinden biridir: geçmiş olaylar, gelecekteki ihtiyaçlar için yeni görünümler üretebilir.
Bu deneyler, olay-tabanlı mimariyi — EDA’nın ayrıştırmasını, CQRS’in projeksiyonunu ve event sourcing’in yeniden oynatmasını — somut bir deneyime dönüştürür. Olayların bileşenleri ayrıştırdığını, okuma modellerini beslediğini ve geçmişten yeni görünümler inşa edebildiğini kendi gözünüzle görmek, bu mimarileri kalıcı olarak öğretir. Ve bu deneyim, gerçek sistemleri olaylar etrafında kurmaya hazırlar.
Üç Gözlem Sorusu
Bu bölümü ve Kısım IX’u, sizi kendi mimarinize eleştirel bir gözle bakmaya davet eden üç soruyla kapatıyoruz. Bu soruların cevapları, çoğu zaman olaylardan yararlanabilecek ama hâlâ doğrudan çağrılara dayanan bir mimariyi açığa çıkarır.
Birincisi: Sisteminiz olaylarla mı iletişiyor (EDA), yoksa bileşenler birbirini doğrudan mı çağırıyor? Olaylara geçmek, size gevşek bağlılık ve genişletilebilirlik — yeni bir bileşeni mevcut olayları dinleterek ekleme yeteneği — kazandırır mı?
İkincisi: Okuma ve yazma ihtiyaçlarınız belirgin biçimde farklı mı? Okumalar yazmalardan çok daha mı baskın, ya da farklı şekiller mi gerektiriyor? CQRS, bunları ayırmaya değer mi, yoksa tek bir model yeterince iyi mi hizmet ediyor?
Üçüncüsü: Sisteminizde tam denetim, yeniden oynatma ya da zamansal sorgular değerli mi — özellikle finansal ya da düzenleyici nedenlerle? Event sourcing’in karmaşıklığı, bu yetenekler için değer mi, yoksa sizin durumunuzda gereğinden fazla mı olur?
Kapanış ve Kısım IX’in Sonu
Bu bölümde, sistemleri olaylar etrafında kurmanın kapsamlı yaklaşımlarını derinlemesine inceledik. Event-Driven Architecture’ı (olaylarla iletişim), olay ve komut ayrımını, EDA’nın fayda ve bedellerini gördük. CQRS’i (okuma/yazma ayrımı), olaylarla projeksiyonu ve fayda/bedellerini işledik. Event sourcing’i (olayları gerçeğin kaynağı yapmak) ve fayda/bedellerini öğrendik. Son olarak, üçünün katmanlı birlikteliğini ve olay-tabanlı bir bahis sistemini gördük.
Ve böylece, Kısım IX — Mesajlaşma Desenleri tamamlandı. Bu Kısım boyunca, RabbitMQ’yu bir araçtan bir mimari omurgaya dönüştüren desenleri öğrendik. ↗ Bölüm 35'te temel desenleri (work queue, pub/sub, RPC) gördük. ↗ Bölüm 36'da outbox ile güvenilir yayımı, ↗ Bölüm 37'de saga ile çok servisli tutarlılığı işledik. Ve bu bölümde, EDA, CQRS ve event sourcing ile tüm mimarileri olaylar etrafında kurmayı öğrendik. Desenler, RabbitMQ’nun yapı taşlarını gerçek mimari çözümlere dönüştürdü.
Kısım IX’in büyük dersi şudur: desenler, RabbitMQ’yu bir mesaj taşıyıcıdan bir mimari temele yükseltir. İşi dağıtmaktan olayları yayınlamaya, çok servisli işlemleri yönetmekten tüm bir olay-tabanlı sistemi kurmaya — mesajlaşma ve olaylar, modern dağıtık mimarilerin temelidir. RabbitMQ’yu derinlemesine bilmek, bu desenleri kurmak için gereken yapı taşlarını bilmektir; desenleri bilmek ise, o yapı taşlarını mimariye dönüştürmektir.
Artık RabbitMQ’nun hem derinliklerini hem de üzerine kurulan desenleri biliyorsunuz. Geriye, bu bilgiyi gerçek çerçevelerde — kod düzeyinde — uygulamak kalıyor. Java/Spring dünyasında RabbitMQ’yu nasıl kullanırsınız? PHP/Symfony dünyasında? Ve RabbitMQ’yu, bir başka popüler mesajlaşma sistemiyle, Kafka ile nasıl karşılaştırırsınız? ↗ Bölüm 39 — Spring AMQP Derinlemesine ile Kısım X — Uygulama’ya geçiyoruz. O Kısım’da, RabbitMQ’yu Spring AMQP ve Symfony Messenger ile pratikte kullanmayı ve Kafka ile karşılaştırmayı göreceğiz. Desenleri öğrendik; şimdi, onları gerçek çerçevelerde hayata geçiriyoruz.









