Photo by Wonderlane on Unsplash

Teknik Muhabbetler #3 (CQRS)

Furkan Güngör
Mobiroller Tech
Published in
6 min readAug 31, 2020

--

Söz konusu muhabbet etmek ise ekip arkadaşlarımla gün içerisinde konuştuğumuz, tartıştığımız ve sonuçta “evet sanırım Mobiroller için şu an en iyisi bu, hadi uygulayalım.” cümlesinden sonra kendimi klavye başında, gün içerisinde yaptığımız bu muhabbetleri yazarken görmek oldukça heyecan verici oluyor. :) Evet belki bu yazıları ben yazıyor gözükebilirim ancak bu yazılarda okuduğunuz her cümle aslında ortak bir ağızdan çıkıyor, kısacası takım çalışması işte… :)

Bugün aktarmaya çalışacağım konu CQRS, aslında CQS prensibinden türüyen bu terimi anlatmanın en iyi yolu öncelikle CQS prensibini anlatmak olacaktır.

Bu iki prensibi ele alırken, genel olarak hangi problemi çözmeye çalıştıklarını, ne zaman uygulamanız gerektiğini ve Mobiroller sistemlerinde bu terimlerin nasıl hayat bulduğunu açıklamaya çalışacağım.

CQS (Command Query Separation)

CQS örnek çizim

Günlük hayatta nasıl kod yazdığımızı bir gözden geçirelim, genellikle bir veri tabanı ile iletişime geçen Repository katmanına gider ve ilgili interface üzerine bir metot ekleriz, ardından Service katmanına ulaşır ve mapping, validation ve business logiclerin bulunduğu bir kod yazarız. Sonuç olarak ilgili endpointe istenilen datayı aktarırız. Bu kısır döngü devam ederken dikkat etmemiz gereken nokta tek bir interface üzerinde hem veriyi okuyan hem de veri üzerinde update,delete,insert gibi komutlar çalıştıran metotların bulunmasıdır.

Kısacası Command ve Query objelerinin tek bir interface üzerinden ilerlemesi CQS prensibinin ortaya çıkmasını sağladı diyebiliriz.

CQS bize metotlarımızı nasıl ele almamız gerektiğini açıklar, biraz daha basitleştireyim. CQS der ki;

Bir metot objenin durumunu değiştiriyorsa geriye bir sonuç dönmemelidir eğer geriye bir sonuç dönüyorsa o zaman objenin durumunu değiştirmemelidir.

Developer friendly olarak anlatacak olursam bir metot objenin durumunu değiştiriyorsa void olmalıdır eğer değiştirmiyorsa mutlaka bir return tipi olmalıdır.

Eğer bu prensip hoşunuza gittiyse ilk yapmanız gereken uygulama içerisinde bulunan metotlarınızı ikiye ayırmak olacaktır.

  • Command : Objenin veya sistemin durumunu değiştirenler
  • Query : Sadece sonuç dönen objenin veya sistemin durumunu değiştirmeyenler

Bu ayırımları yaptığınızda kodunuzun daha readable ve reusable olduğunu göreceksiniz. Metotlarınızın genel bir standart içerisinde olması özellikle uygulamaya yeni katılan arkadaşların daha hızlı adapte olmasını sağlayacaktır.

CQRS (Command Query Responsibility Separation)

CQRS ise CQS’in daha gelişmiş bir versiyonudur. Hedefleri aynı olmasına rağmen ikisi birbirinden çok farklı şekilde ele alınır. CQRS Command ve Query objelerini application seviyisinde ayırmamız gerektiğini söyler.

CQS uygulandığında Command ve Query nesneleriniz birbirinden ayrılmış olur. CQRS ise bu iki nesnenin application seviyesinde ayrılmasını ister yani sadece metotlar değil uygulamalar hatta veri tabanları bile farklı olmalıdır.

Kulağa biraz garip gelebilir ilk duyduğumda bana da biraz garip gelmişti. :)

CQRS’in ne zaman kullanılması gerektiğini açıkladıktan sonra günlük hayattan örnekler vererek kafanızda oluşan soru işaretlerini yok etmeye çalışacağım. :)

Klasik bir e-ticaret sistemini ele alalım, bu domainde en güzel entity kesinlikle order olacaktır, gün boyunca bu tabloya veriler yazılıyor, siliniyor, okunuyor ve güncelleniyor. Eğer projenizde order tablosuna kayıt eklenirken aynı tablo üzerinde okuma işlemleri yavaşlıyorsa o zaman CQRS’i düşünmemiz gerekiyor demektir.

Farklı bir açıdan yaklaşalım, servislerinizde Read işlemleri Write işlemlerinizden daha maliyetli olabilir bu durumda Read işlemleri için iyi bir sonucu olan MongoDB teklonojisini kullanmak isteyebilirsiniz.

CQRS ile teklonojik bağımlılıklarınızı minimum seviyeye indirgeyebilirsiniz. Çünkü bu pattern Command ve Query nesnelerini application seviyesinde ayrımamız gerektiğini söyler böylece application seviyesinde ayrılmış olan nesneleriniz için en iyi teklonojiyi seçebilirsiniz.

Kullanmak için gereksinimler nelerdir ?

  • Her zaman olduğu gibi ekip içerisinde bulunan geliştiricilerin zihinsel yaklaşımları oldukça önemlidir. Öncelikle ekip içerisinde bu zihinsel sıçramanın yapılması gerekir yani ekip halinde beyin fırtınası yaparak hangi servislerinizin Write ve Read operasyonları arasında dengesizlik olduğunu belirlemelisiniz.
  • Her şeyin fazlası zarar :) CQRS bir sistemin tamamında uygulanmamalıdır sistem üzerinde iyi bir analiz yapılmalı ve gerekli nesneler üzerinde bu pattern uygulanmaladır.
  • CQRS, yükü okumalardan ve yazmalardan ayırmanıza olanak tanır bu sayede her birini bağımsız olarak ölçeklendirebilirsiniz. Uygulamanızda okuma ve yazma arasında büyük bir eşitsizlik varsa, bu pattern çok kullanışlıdır. Bu olmadan bile, iki tarafa farklı optimizasyon stratejileri uygulayabilirsiniz. Örneğin, okuma ve güncelleme için farklı veritabanı erişim tekniklerinin kullanılmasıdır.
  • Sürekli değişen iş kurallarınız ve kompleks bir sisteme sahipseniz bu pattern iş yükünüzü hafifletebilir.

CQS desenini uygulamaya çalıştığınızda .Net dünyasında bu konuda oldukça popüler olan bir kütüphane ile karşılaşacaksınız, evet bilenlerin ilk aklına gelen o kütüphane :) MediatR

Bu kütüphane ile kolay ve basit bir şekilde uygulamanız içerisinde mesajlaşma işlemini gerçekleştirebilirsiniz.

Yazının sonraki satırlarında MediatR kütüphanesini nasıl kullanabileceğinizi aktarmaya çalışacağım ancak öncesinde Mediator terimini inceleyelim.

Mediator

Artık CQS ile metotlarımızı ikiye ayıracağımızı biliyoruz ancak henüz nasıl yöneteceğimizi bilmiyoruz. Command ve Query olmak üzere nesnelerimizin olacağını söylemiştik. Bu nesneleri işaretleyip ardından bir kontrol mekanizmasından geçirmeliyiz. Mediator bu cümlede kontrol mekanizması oluyor.

Mediator örnek çizim

Tahmin edileceği üzere örnek çizimde arabalar nesnelerimizi temsil ederken ortada duran ve bir isim bulamadığım ama trafik polisine benzeyen yapı ise mediator desenini temsil ediyor. :)

Mediator Command nesnelerini CommandHandler’a Query nesnelerini ise QueryHandler’a yönlendirmek ile görevlidir.

Şimdi küçük bir PoC yapalım. :)

MediatR PoC

Install-Package MediatRInstall-Package MediatR.Extensions.Microsoft.DependencyInjection -Version 8.1.0
Startup.cs

Entity olarak Customer nesnesini kullanacağım, CQS ile birlikte metotlarımızın ikiye ayrılması gerektiğini bildiğimize göre yapmamız gereken CustomerCommand, CustomerQuery nesnelerimizi oluşturmak.

Query and Command object for customer

Sadece nesneleri ayırmak yetmeyecektir, Command ve Query işlemleri, aynı interface üzerinde ilerlememesi gerekiyor. Bunun için Command ve Query nesnelerini yakalayacak Handler sınıfları yazmamız gerekecek.

Customer Query and Customer Command Handler

MediatR kütüphanesi ile çalışma zamanında mesajlaşmayı ve nesnelerin Command veya Query olmasına göre nasıl Handle edileceğini belirlemiş olduk. Şimdi yapmak gereken mesajı fırlatmak. :)

CustomerController.cs

Yazının başını hatırlayacak olursak Mobiroller servisleri üzerinde CQS prensibini nasıl uyguladığımızdan bahsedeceğimi söylemiştim.

Mobiroller servislerinde MediatR kullanmıyoruz, aslında servisler üzerinde tam anlamıyla CQS prensibini de uygulamıyoruz, aslında kendi problemlerimize göre ürettiğimiz bir çözüm var.

Servisler içerisinde herhangi bir t anında verinin işlenmesi veya durumunun değişmesi oldukça olağan bir durum ancak bir nesnenin durumunun değişmesi başka bir nesnenin durumunu etkileyebiliyor, bu sebeple SOLID prensiplerinden en sevdiğim olan Single Responsibility ilkesini yerine getirmek için metotlarımızın tamamına tek bir sorumluluk yüklemeye çalışıyoruz.

Ardından Command veya Query işlemini bitiren bu metot bir event fırlatarak işini bitirdiğini kendini dinleyen herkese bildiriyor ve tahmin edileceği üzere onu dinleyen bazen bir Command bazen ise bir Query oluyor. Böylece metotlar üzerinde bu ayrımı kolayca yapmış oluyoruz. Aklınıza şu soru gelebilir fırlatılan eventler nasıl işleniyor ?
Aslında en önemli kısmı burası, çalışma zamanında fırlatılan bu eventleri işlemek ve yönetmek için CAP kullanıyoruz.

Cap microservice mimarisinde servislerin birbiri ile iletişime geçmesinde oluşacak istisnasları çözmek için mevcut veri tabanı ile entegre çalışan Local Message Table felsefesini benimsemiştir.

Microservice mimarisi için geliştirilmiş bu kütüphanenin bize sunmuş olduğu özellikleri kullanarak çalışma zamanında mesajlaşmayı çözmek ile kalmıyoruz, eventlerin işlenmeme veya işlenirken oluşan hatalardan uzak ve rahat bir hayat yaşıyoruz. :)
Çünkü CAP tüm eventleri db seviyesinde saklar böylece hatalı veya işlenmeyen eventleri görebilir ve yönetebilirsiniz. (Biz öyle yapıyoruz. :) )

CAP dikkatinizi çektiyse o zaman buradan daha detaylı bir yazıma ulaşabilirsiniz.

CQS ile ilgili aklınızda soru işareti kalmadığına inanarak kahvemden bir yudum alıp CQRS deseninin tam olarak ne dediğini anlatmak için parmaklarımı klavyeye yaklaştırıyorum. :)

CQRS CQS gibi Command ve Query nesnelerinin birbirinden ayrılmasını ister ancak bunları application seviyesinde ayırmaya odaklanır.

CQRS örnek çizim

Örnek çizimde gösterildiği gibi Command ve Query nesneleri veri tabanı seviyesine kadar her seviyede ayrılmalıdır.
Veri tabanları bile farklı olan, sistemin herhangi bir ‘t’ anında verileri nasıl tutarlı olabilir?
Tam burada Event Sourcing devreye giriyor, iki farklı uygulamayı haberleştirmemiz gerekiyor, yani Write Application datayı veri tabanına kaydedince bir event fırlatarak görevini tamamladığını bildirir.
Read Application tarafında bu event dinlendiği sürece data yakalanır ve Read DB güncellenir. Böylece ‘t’ anında sistem tutarlıdır denilebilir.

CQRS prensibinde en önemli konu olan tutarlılık terimini şimdiye kadar çok farklı ele alan projeler gördüm, bunlardan bahsetmek isterim.

  • Write servisi datayı yazar ancak Read servisinde oluşabilecek bir hata sonucunda verinin işlenmemesi için eklediği bu kaydı pasif yapar, Read servisi başarılı bir şekilde veriyi işledikten sonra bir event fırlatır ve Write servisi kendi verisini aktif hale getirir.
  • Write servisi datayı yazar ve Read servisinin dinlediği eventi fırlatır.Ancak hata oluşma ihtimali ile her gece migration çalışır ve veri tabanları eşitlenir.
  • Write servisi datayı yazar ve Read servisinin dinlediği eventi fırlatır, eğer event işlenemez ise Error eventi fırlatılır ve bir message broker üzerinde bu event saklanır, burayı dinleyen başka bir servis hatayı alır ve ilgili ekibe yönlendirir, aksiyonlar direkt olarak geliştiriciler tarafından alınır.

Yazının başında ne zaman kullanmalıyız sorusuna cevap ararken öğrendiğimiz gibi CQRS tamamiyle bir sistem üzerinde uygulanmamalıdır, analizler yapılmalı ve gerekli olan nesneler ayırılmalıdır ancak CQS tamamiyle bir sistem üzerinde uygulanabilecek bir patterndir.

--

--

Furkan Güngör
Mobiroller Tech

Solution Developer — I want to change the world, give me the source code.