CQS ve CQRS nedir? Clean Architecture ve DDD (Domain-Driven Design) gibi mimariler ile birlikte nasıl bir uyumu vardır?

Enes Emre Demir
6 min readApr 24, 2021
CQRS ve DDD kullanarak microservice oluşturmak.

Merhabalar herkese,

Bu yazımda CQRS nedir ve clean architecture ve DDD gibi mimariler ile uyumundan bahsedeceğim.

Ancak CQRS hakkında konuşmadan önce CQRS’in atası olan CQS hakkında konuşmak istiyorum.

CQS Nedir?

Command–query separation yani CQS, imperative computer programming’in bir ilkesidir. Bertrand Meyer tarafından 1980 yılında Eiffel programlama dili üzerine yaptığı çalışmanın bir parçası olarak tasarlandı.

Her metodun ya bir eylemi gerçekleştiren bir komut (command) ya da veri döndüren bir sorgu(query) olması gerektiğini, ancak her ikisinin birden olmaması gerektiğini belirtir.

Bertrand’ın sözleri ile:

“Asking a question should not change the answer.”
(Soru sormak cevabı değiştirmemelidir.)

CQS’in başlıca ilkeleri şunlardır:

  • Bir sistemin verilerini sorgulayan işlemler, herhangi bir yan etkiye, yani durumu değiştirmeye neden olmamalıdır. Bu işlemlere “queries” denir. Query’ ler verileri DTO’lar (Data Transfer Object) aracılığıyla döndürür.
  • Bir sistemin verilerini değiştiren işlemler veri döndürmemelidir. Bunlar yan etkiler oluşturmalı, sistemin durumunu değiştirmeli ve ardından tamamlanmalıdır. Bu işlemlere “commands” denir.
  • Pratikte command’ler, yeni oluşturulan bir entity’nin ID’si gibi küçük bir metadata döndürebilir, ama daha fazlasını yapamaz.
  • Command’leri execute etmenin başka bir sonucu bir hata durumu olabilir, bu durumda command “throw exception” yapmalıdır.

Şimdi CQRS’e geçebiliriz.

CQRS’e Giriş

CQRS, “Command/Query Responsibility Segregation” anlamına gelir. CQRS, Greg Young tarafından 2010 yılında geliştirilmiştir ve CQS temellidir. CQRS, “Clean Architecture” ve “N-Layer Architectures” gibi mimarilerde bulunan WebUI gibi (Controllerların bulunduğu, client ile iletişimin olduğu yer) üst düzey katmanların stack aracılığıyla application katmanı gibi diğer katmanlarla iletişim kurmasını sağlayan bir tasarım kalıbıdır.

Örnek bir “Clean Architecture” yapısı.

ör. WebUI katmanındaki controller’lar, application katmanı component’ları tarafından execute edilen command’leri ve query’leri çağırır. CQRS, “Clean Architecture” gibi mimariler ile güzel bir uyum gösterir çünkü CQRS behavioral (davranışsal) bir pattern’dır.

Mimariler “Ne ?” sorusuna cevap verir.

CQRS “Nasıl ?” sorusuna cevap verir.

DDD veya CA uygulamak için CQRS kullanmak zorunlu mu?

DDD veya CA mimarilerini uygulamak için CQRS kullanmanız gerekmediğini belirtmek isterim, ancak kullanmamak için bir sebebiniz var mı? Alternatif bir yaklaşım, orchestration logic’inizi, doğrudan controller’larımıza inject edilen application layer servisleri içinde encapsulate etmek olabilir. Her şey düzenli ve “Dependency Inversion” ilkesiyle uyumlu. Ancak, katmanlar arasındaki iletişim sürecinin kendisini soyutlayarak CQRS’in sağladığı faydaları kaybettiniz.

Peki ya CQS?

CQS, doğrudan bir CA veya DDD mimarisinde uygulanabilir. Bunu yapmak için, genellikle controller metodlarınızı commands / queries (yani yazma / okuma) işlemlerine ayırırsınız ve ikisi arasındaki ayrımı asla ihlal etmezsiniz. Amaç, yalnızca verileri kaydetmek veya diğer CRUD işlemlerini gerçekleştirmek yerine behavior yani davranış işleyen task-based interface oluşturmaktır.

Dikkat edin: CQS ve CQRS’ in DDD ile kesiştiği yer burasıdır. Operasyonlar genellikle, içinde çalıştığınız bounded context’in ubiquitous language(ortak dil) kullanan iş süreçlerinden sonra adlandırılacaktır. Bu basit, zarif tasarımın aynı zamanda Single Responsibility ilkesini de desteklediğini unutmayın. çünkü her işlem birbiri ile daha uyumludur ve UI işlemlerine daha iyi cevap verir. Bu, kullanıcı deneyimi (UX) geliştirmeye yönelik bir geliştirme sürecini kolaylaştırır.

CQRS ve CQS arasındaki farklar neler?

CQRS ve CQS arasındaki ilişkiyi anlatan çok sevdiğim bir söz vardır:

“CQRS is basically CQS but on steroids.”

(CQRS, CQS’in steroid almış halidir.)

CQRS command’leri ve query’leri alır ve bunları first class object’lere dönüştürür. CQS kullanan bir tasked-based interface kolayca CQRS’e dönüştürülebilir çünkü logical separation zaten oradadır. İki pattern arasındaki en büyük fark, CQS’de command’lerin / query’lerin metod olmasıdır; CQRS’de bunlar birer modeldir. Buradaki ayrım önemlidir. İşlemleri model olarak ele alarak, uygulamanıza büyük derecede scalability(ölçeklenebilirlik) katmış oluyorsunuz. CQRS kullanmanın göz ardı edilmemesi gereken bir başka yan ürünü de command’lerin / query’lerin kendilerinin serializable data contracts haline gelmesidir.

CQRS kullanmanın birkaç önemli faydası şunlardır:

  • Ölçeklenebilirlik: Genellikle sisteme karşı yazma işleminden çok daha fazla okuma işlemi vardır (Pareto Prensibine güzel bir örnek). CQRS altında query’ler kendi stack’lerine bölünebilir ve command’lerden bağımsız olarak ölçeklendirilebilir.
  • Performans: Tightly coupled modelde mümkün olmayan optimizasyonlar yapabilirsiniz.
  • Sadelik: CQRS’i mimarinizde kullanarak başlangıçta uygulamanıza küçük bir miktar karmaşıklık katarsınız, ancak uygulamanız iş taleplerini karşılamak için büyüdükçe, gelecekte bunun karşılığını misli ile alırsınız. Zamanla, çok daha fazla bakım yapılabilir ve karmaşıklığı daha iyi yönetebilirsiniz.

CQRS Nasıl Çalışır?

Commands / queries WebUI katmanında (controller metodlarının içinde) somutlaştırılır ve daha sonra business orchestration mantığını gerçekleştiren ve ilgilendiğiniz görevi yürüten application katmanına iletilir.

CQRS uygulamanın bir yolu, command / query parametrelerini ve ait oldukları iş mantığını işleyen (iş mantığını handle eden demek daha mantıklı olacaktır)bir object’e sahip olmaktır. Bu object, dependency injection kullanılarak her controller’a inject edilir veya bir tür factory kullanılarak oluşturulur. Commands / queries object’inde bir Execute() metodu çağrılır ve sonuç alınır. Açıkçası bu yöntem benim hoşlandığım bir yöntem değil.

CQRS'i uygulamanın daha iyi bir yolu, command / query işleyicilerinden(handlers) ayırmak ve command / query object’lerini ilgili işleyicilerine göndermek için bir tarz “in-process messaging service” kullanmaktır. Bu GOF Mediator Pattern’ine bir örnektir.

Mediator Pattern Nedir?

Mediator, object’ler arasındaki dependency’leri azaltmanıza izin veren behavioral bir tasarım kalıbıdır. Mediator pattern, object’ler arasındaki doğrudan iletişimi kısıtlar ve onları yalnızca bir aracı nesnesi aracılığıyla işbirliği yapmaya zorlar. Bu aracı nesneye mediator denir.

Mediator Pattern’nin gerçek hayattaki en güzel örneklerinden biri ve büyük ihtimal sizin de çok görmüş olduğunuz havalimanlarında bulunan kontrol kuleleridir.

Pilotlar kendi aralarında iletişim yapmazlar. Onları kule yönlendirir

Havaalanı kontrol alanına yaklaşan veya çıkan uçak pilotları birbirleriyle doğrudan iletişim kurmazlar. Bunun yerine, uçak pistine yakın bir yerde yüksek bir kulede oturan bir hava trafik denetleyecisi ile konuşurlar. Hava trafik denetleyecisi olmasaydı, pilotlar diğer pilotlar ile iniş önceliklerini tartışarak havalimanının çevresindeki her uçağın farkında olması gerekirdi. Bu muhtemelen uçak kazası istatistiklerini arttırdı.

Bu pattern’ı uygulamanın faydaları var, ancak en önemli ikisi şudur:

  • Commands / Queries ilgili işleyicilerine bağlamak için boilerplate kod yazmak zorunda kalmadığınız için kodunuzu basitleştirir.
  • Uygulamanızda, error-handling, caching, logging, validation, retry ve daha fazlası gibi cross-cutting concern’leri inject edebileceğiniz bir task execution pipeline oluşturdunuz. Bu çok önemli bir özellik.

Bunu nasıl uygulayacağınız size kalmış. Bir yandan, kendi in-process messaging service’inizi kullanabilirsiniz (Bunu önermem) yada MediatR gibi
önceden oluşturulmuş bir messaging framework’den yararlanabilirsiniz (Asıl önerim bunu kullanmanızdır). Messaging pipeline kullanmaya alternatif bir yaklaşım, command’lerinize / query’lerinize ek işlevler eklemek için decorator pattern gibi bir şey kullanmak olabilir. Şahsen bu benim önerdiğim bir yöntem değil.

Command’ler

CQRS command’lar her zaman şimdiki zamanın emir kipinde adlandırılır - ör. ActivateProductCommand. Command’ler sistem durumunu değiştirir ve basit metadata yanıtları döndürürler veya bir exception oluştururlar.
Command’ler reddedilebilmesi açısından event’lerden farklıdırlar; event’ler reddedilemez. Command’ler, genellikle Application katmanı aracılığıyla Domain katmanıyla etkileşime girer. Bu önemlidir, çünkü Domain katmanı tüm iş mantığını içerir ve sistemi sürekli bir durumda tutmaktan sorumludur. Son olarak, command’lerin genellikle idempotent olması gerekir. Buna iyi bir örnek, bir müşterinin kredi kartından ödeme alan bir komutunuz olması olabilir — komut yeniden uygulanabilir olmalı ve müşteriden birden fazla ödeme almamalı.

Query’ler

Query’ler de şimdiki zamanın emir kipinde adlandırılır ve genellikle "Get" ile başlar - ör. GetCustomerListQuery. Query’ler sistem durumunu değiştirmez, yalnızca verileri döndürürler, genellikle bir DTO biçiminde. Döndürülen DTO'nun özellikleri, veriler muhtemelen normalize edilmemiş bir veritabanı sorgusundan geri geleceği için 1. normal forma yakın yapılandırılmıştır ve döndürülen DTO genellikle kullanıcının ekranıyla eşleşecektir.

Greg Young, çoğu zaman query’lerin domain katmanını bypass etmesi gerektiğini belirtir. Bunu biraz daha açalım. Neden application katmanından UI katmanına doğrudan geçmek isteyelim? İlk olarak, veri modeli kullanıcı girdisinden daha güvenilirdir ve her zaman tutarlı olduğu varsayılır, bu nedenle veritabanından gelene validation yapmaya gerek yoktur. İkinci olarak, query’ler durumu değiştirmez, dolayısıyla bu manipülasyonu yapan domain logic’in bir faydası yoktur. Üçüncüsü, okumalar (query’ler) bir sistemde yazılmalardan (command’ler) daha sık meydana gelecektir, bu nedenle hızlı ve verimli olmaları gerekir. Bu son noktayla birlikte, çoğu uzman query tarafında ORM bile kullanılmamasını tavsiye ediyor. Özetle, domain katmanından geçen query’lere izin verilir, ancak bu bir istisnadır.

Hem Command’ler hem de Query’ler için Ek Bilgiler

  • “Peki ya CQS ?”Adlı bölümde belirtildiği gibi, hem command’ler hem de query’ler ubiquitous language’a uygun olarak adlandırılmalı ve CRUD yerine task-based işlemleri temsil etmelidirler.
  • “Command” ve “Query” ekleri opsiyoneldir, bu nedenle isim seçmek size kalmış. Sadece adlandırmanızın tutarlı olduğundan emin olun.
  • Command’lerden / query’lerden diğer command’leri / query’leri çağırmayın. Eğer yaparsanız ileride başınızı ağrıtabilir. Bir command için veritabanından veri almanız gerekiyorsa, doğrudan bir ORM kullanarak veya başka bir yaklaşımda bulunarak veritabanını sorgulamanız gerekir. Bunun, diğer ilkelerin desteklemek için belirli ilkelerin ihlal edilmesi gereken önemli bir örnek olduğuna dikkat edin. Bu durumda, stack’mizin iki tarafını tehlikeli bir şekilde birbirine bağlamadan ve her şeyi bakımsız kod yığınına dönüştürmekten kaçınmak için DRY(Don’t Repeat Yourself) ilkesini ihlal ediyoruz. Kendinizi loose coupling ile DRY arasında seçim yaparken bulursanız, loose coupling’i seçin.

Bu yazımda elimden geldiğince sizlere CQRS hakkında bilgi vermeye çalıştım ve mimariler ile uyumunu anlattım. Bir sonraki yazılarımda burada bahsi geçen konular hakkında ufak bir uygulama geliştirmeyi planlıyorum. Hepinize faydalı olması dileğiyle.

Saygılarımla.

--

--