CQRS Tasarım Kalıbı

Engin UNAL
Bilişim Hareketi
Published in
5 min readFeb 20, 2022

Mimari tasarım kalıplarından olan CQRS ile tasarım kalıpları serimize devam ediyoruz. Temelde sade bir bakış açısı sunmasına rağmen çok çeşitli alanlarda kullanılması nedeniyle farklı anlatımlarla biraz zorlaştırılsa da bu yazıda aslında amaçlananı vermeye çalışacağım. İyi okumalar.

CQRS, Command Query Responsibility Segregation kelimelerinin kısaltılmışıdır. Adı üstüne komut ve sorgulama işlemlerinin ayrılması prensibini temel alır. Komutları veri üzerinde değişiklik yapan kod blokları(create, update, delete), sorguları ise veriyi okuyan kod blokları olarak düşünebiliriz. Aslında tam olarak şudur; Command yapıları bir durumu değiştiren yapılar, query yapıları ise durumu okuyan yapılardır. Bu yazıda command ve query kavramlarının etrafında dolanıp bunun neler getirdiğini anlamaya çalışacağız.

CQRS, bir sistemin/uygulamanın eylemlerini temelde command ve query olarak iki kategoriye ayırır, böylece command veya query kendi sorumluluğuna uygun işlemleri gerçekleştirecek şekilde dizayn edilir. Geldiğimiz noktanın özeti:

Command, bir durum(state) üzerinden değişiklik yapan nesneler. Query ise veri okuyan nesnelerdir. CQRS ise bunların sorumluluklarının ayrılması gerektiğini söyler.

Buraya kadar gayet net ve anlaşılabilir geldiyse devamını okumanıza gerek kalmadı. Teorik olarak bunlar yeterlidir. Fazla gereklilikler ve şartlar over engineering yolunda atılan adımlara dönüşmektedir. Yazının sonlarına doğru buna değineceğim. Şimdi biraz daha derinden bakmak isteyenler için devam edelim.

CQRS, benzer akronime sahip CQS tasarım kalıbını temel almakta ve CQS’i genişleterek komut ve sorgu için ayrı nesneler önermektedir. CQS, Command Query Separation tasarım kalıbı Bertrand Meyer tarafından 80'li yıllarda Eiffel programlama dili üzerinde çalıştığı sıralarda geliştirdiği bir tasarım kalıbıdır. Bakalım ne demiş:

A command (procedure) does something but does not return a result.

A query (function or attribute) returns a result but does not change the state.

Ve başka bir yazıdaki açıklamada :

Every method should either be a command that performs an action, or a query that returns data to the caller, but not both.

Görüldüğü üzere command metodları, sistemin durumunu(state) değiştirir fakat geriye sonuç dönmez. Query metodları ise sistemin durumu(state) değiştirmez ve geriye sonuç döner. Artı olarak tüm metodlar command veya query kategorisinden birinde olmalıdır, iki işi de yapmamalıdır.

2010 yılında Greg Young CQRS ismini verdiği tasarım kalıbında ise CQS’in prensiplerini genişleterek tüm sisteme uygulanabilir bir çözüm olarak sunmuşur. Kendisi şöyle açıklıyor:

Command and Query Responsibility Segregation uses the same definition of Commands and Queries that Meyer used and maintains the viewpoint that they should be pure. The fundamental difference is that in CQRS objects are split into two objects, one containing the Commands one containing the Queries.

Young’ın belirttiği üzere temel fark, CQRS’deki nesneler, biri Komutları içeren diğeri Sorguları içeren iki nesneye bölünmektedir.

CQS, mikro seviyede metodların tasarımı konusunda bir yaklaşım ile komut-sorgu ayrımını yaparken; CQRS, bu konsepti daha üst seviyede ele alıp okuma ve yazma modellerini içeren farklı nesneler oluşturup bunları birbirinden ayırmayı amaçlar. Bu şekilde CQRS, komut ve sorgulama sorumluluklarını sistem içerisinde dikey olarak ikiye böler. Böylece durum değişikliklerini yapan katmanı command handler yapılarıyla, sorgulama yapan katmanı ise query handler yapılarıyla yönetir.

Evet bu kadar tanım yeterli daha net görebilmek için CQS örneğini kod üzerinde uygulayarak görelim.

BookStoreCQS sınıfımız mevcut, bu sınıf içerisinde Sell ve GetList metodları bulunmakta. Sell metodu satışı yapılan kitap ile ilgili ve verinin durumunu(state) değiştireceği için command, GetSoldList metodu ise state değişikliği yapmayacak ve sadece veri döneceğinden query tanımına karşılık gelir. Görüldüğü üzere aynı sınıf içerisinde iki farklı metod üzerinde CQS örneğimizi uygulamış olduk.

Şimdi aynı işlemi CQRS pattern’e uygun şekilde kodlayalım.

İki sınıfımız oldu biri Command diğeri Query sorumluluğunu almakta. Command sınıfımız altında sadece state değiştiren metodumuz olan Sell metodu bulunmakta. Query sorumluluğu olan sınıfımız içerisinde ise listemiz dönmekte. Yukarıdaki örnekler tabi ki tamamen konseptin anlaşılabilmesi adına olabildiğince küçültülmüş ve ek kavramlar içermeyen basit örneklerdir.

Başka şekilde ifade edersek; CQRS pattern bize Command ve Query için keskin bir ayrım yapmamızı söyler. Yani birbirinden ayrı Command Handler ve Query Handler nesnelerini kullanmalıyız. Command Handler yapıları komutların çalıştırılması ve state değişimi işlemlerinden sorumlu yapılardır. Query Handler yapıları ise istek yapılan sorgulara sonuç döndürmekten sorumlu yapılardır.

Buraya kadar yapılan tanımlar ve açıklamalarda Command ve Query sorumluluklarının ayrı olması ve genel işleyiş özelliklerinden bahsedilirken verinin saklanacağı yer veya saklama şekli gibi konularda herhangi bir yönlendirme olmaması oldukça önemli bir noktadır. Bazı yanlış anlamaların temeli de bu noktada ortaya çıkmaktadır.

CQRS Ne Anlatıyor? Ne Anlıyoruz?

  • CQRS ile okuma ve yazma için iki farklı veritabanı kullanmanız gerekmez. İster İlişkisel Veritabanı kullanın veya aynı veritabanındaki tek tablo üzerinden okuma ve yazma işlemi gerçekleştirin CQRS bununla ilgili bir kısıt koymaz.
  • CQRS uygulayacaksanız DDD yaklaşımı kullanmak zorunda değilsiniz. Böyle bir zorunluluk veya gereklilik yoktur. Tam tersi de olabilir, yani DDD uygularken mutlaka CQRS kullanmanızı gerektiren bir durum söz konusu değildir. Böyle bir gereklilik yoktur.
  • Event Sourcing uygulamak zorunda da değilsiniz. Evet CQRS denilince Event Sourcing uygulamaları çok yoğun görülmekte fakat böyle bir gereklilik veya zorunluluk da yoktur. Event Sourcing olmadan da CQRS uygulayabilirsiniz.
  • Message Broker ve Message Bus yapıları kullanma gereksinimi de yoktur.

Özetle kurguladığınız yapılardaki gereksinimlere göre bu teknolojileri seçmelisiniz. CQRS uygulamaya karar verildiğinde yukarıdaki yaklaşımları ve teknolojileri de projeye ekleme zorunluluğu yoktur.

Yazma ve Okuma Veritabanlarını birbirinden ayırmak sisteminiz için performans artırıcı getiriler sağlayabilir, örneğin yazma ve okuma işlemleri için iki farklı teknolojiyi NoSql ve ilişkisel veritabanı kullanabilirsiniz. CQRS uygulamak bu tip ayrıştırmaları daha rahat gerçekleştirmenize imkan tanır. Yazma ve okuma veritabanlarını ayırmak devamında başka mimari düzenlemeleri de getirecektir. Örneğin yazma veritabanındaki değişikliklerin okuma veritabanına aktarılması gibi gereksinimleri de message queue(RabbitMQ, Kafka, ..) ile sağlayabilirsiniz.

Bu tip mimari düzenlemeleri çeşitlendirebilirsiniz ama bunlar CQRS’in getirdiği gereklilikler değildir. Bu tamamen tasarımınız ile ilgili bir durumdur. CQRS’in getirdiği esneklik nedeniyle mimarideki değişiklikler daha kolay gerçekleştirilebilmektedir.

Yaklaşım olarak CQRS kullanmak sizi gittikçe Event Sourcing tasarım kalıbını da kullanmaya itecektir. İki yaklaşımın birbiriyle uyumunu ve beraber iyi partner olduklarını Greg Young da ifade etmiştir. CQRS ve Event Sourcing kavramlarının Greg Young tarafından aynı zamanlarda açıklanması da birbiriyle olan sıkı ilişkisini anlatmaya yeterlidir. Fakat unutulmamalıdır ki kendisinin de çok yerde açıkladığı gibi iki yaklaşımın birlikte uygulanması gibi bir gereksinim veya zorunluluk elbette yoktur.

Artıları ve Eksileri Neler?

  • Ölçekleme, optimizasyon daha kolay yapılabiliyor. Diyelim ki sisteminizde okuma yükü çok fazla ve yazma yükü buna kıyasla düşük olsun, okuma yapılan veritabanlarını yazma yapılanlardan ayırarak ve daha fazla sistem kaynağı atayarak çözüm üretebilirsiniz. Veya okuma yapılan veritabanları ile yazma yapılan veritabanlarını farklı teknolojilerde seçip tasarlayabilirsiniz. Bu gibi çözümleri uygulayabilme esnekliği getirmektedir.
  • Okuma ve yazma operasyonlarını ayırdığınızda sisteminizdeki okuma ve yazma davranışlarını ve dolayısıyla kodlarını da ayırmış olmaktasınız. Bu ayrım, kod karmaşasını da düşürücü fayda sağlamakta. Diyelim ki okuma işlemlerinde bazı kod değişikliklerinin yapılması gerekiyor. Okuma kısmındaki değişikliklerin sistem veya veri üzerinde değişikliğe neden olmaması yani sistemin durumunu değiştirmeyecek olması(state’i command değiştirir) sistem genelini etkileyen sürprizlerle karşılaşmanızın önüne geçecektir. Uzun cümlenin özeti; Query düzenlemesi Command tarafını bozmayacağından oralarla sorun yaşamazsınız.
  • Sistemin tasarımında davranışlar arasında ayrıma, daha modüler, bakımı daha kolay ve loose couple yapılar kurmanıza yönlendirir.
  • Kod yükünü arttırır. Daha fazla satır kod, daha çok sınıf yazılmasına neden olur.
  • Modellerin çift olarak tanımlanması, veya dbcontext gibi yapıların birden çok üretilmesi gibi klasik veya aşina olduğumuz duruma pek uygun olmayan kullanımlara neden olabilir. Julie Lerman’ın bu yazısını da incelemenizi öneririm.

Temel olarak bu şekilde özetleyebilirim. Okuduğunuz için teşekkürler.

Hoşçakalın

Engin ÜNAL

--

--