CQRS, Command Query Responsibility Segregation‘ın kısaltılmış halidir. 'Command' ve 'Query' sorumluluklarının ayrılması prensibini esas alan bir yaklaşımı savunmaktadır.
Biliyoruz ki, bir uygulama üzerinde kullanıcıdan gelen istekler iki türlüdür. Gelen istek(Request), ya mevcuttaki bir veri üzerinde manipülasyon yapar veya olmayan bir veriyi oluşturur ya da mevcut veri üzerinde herhangi bir işlem yapmaksızın direkt okunmasını sağlar. Yani gelen istek ya bir salt okuma işlemi yapar ya da diğer işlemleri yapar. İşte bu işlemlerden read işlemi yapacak olan isteklere Query, diğerlerine Command denmektedir.
-
Command Olmayan veriyi oluşturan ya da var olan bir veri üzerinde güncelleme veya silme işlemi yapan isteklerdir. Örn: INSERT UPDATE DELETE
-
Query Mevcut verileri sadece listelemek, okumak yahut sunmak için read işlemi yapan isteklerdir. Örn: SELECT
CQRS, uygulamalarımızda bu istekleri karşılayacak olan yapılanmaları birbirinden ayırmamızı önermektedir.
CQRS, verileri eklemek, silmek ve güncellemek için 'Command' sınıflarını, okumak için ise 'Query' sınıflarını kullanmaktadır.
https://sefikcankanber.medium.com/cqrs-command-query-responsibility-segregation-nedir-16b196376389
İlk olarak bir uygulama oluşturuyoruz yada var olan bir uygulamamız oldugunu varsayıyoruz. İçerisinde 'CQRS' isminde bir klasör oluşturup ardından içerisine 'Commands', 'Queries' ve 'Handlers' isimlerinde klasörler oluşturalım. Ardından tüm klasörlerin içerisine yukarıda görseldeki gibi gelecek olan request’leri ve cevap olarak döndürülecek olan response’ları tanımlayacağımız 'Request' ve 'Response' klasörlerini oluşturalım.
Uygulamada yapılacak olan tüm Command'ları tarif edecek sınıfları barındırmaktadır.
- Request: Yapılacak Command isteklerini karşılayacak olan sınıfları barındırmaktadır.
- Response: Yapılan Command isteklerine karşılık verilecek olan response sınıflarını barındırmaktadır.
Uygulamada yapılacak olan tüm Query'leri tarif edecek sınıfları barındırmaktadır.
- Request: Yapılacak Query isteklerini karşılayacak olan sınıfları barındırmaktadır.
- Response: Yapılan Query isteklerine karşılık verilecek olan response sınıflarını barındırmaktadır.
Uygulamada yapılacak olan tüm Command ya da Query isteklerini işleyecek ve sonuç olarak respose nesnelerini dönecek olan sınıfları barındırmaktadır.
- CommandHandlers: Yapılan Command isteklerini işler ve response'larını döner.
- QueryHandlers: Yapılan Query isteklerini işler ve response'larını döner.
Şimdi Command ve Query sınıflarını oluşturalım.
- CreateProductCommandRequest ve CreateProductCommandResponse class'larımız "Product" ekleme isteklerinde kullanalım.
- DeleteProductCommandRequest ve DeleteProductCommandResponse class'larımız "Product" silme isteklerinde kullanalım.
- GetAllProductQueryRequest ve GetAllProductQueryResponse class'larımız tüm "product" verileri elde edilmek istendiğinde kullanalım.
- GetByIdProductQueryRequest ve GetByIdProductQueryResponse class'larımız id bazlı product sorgulamalarında kullanalım.
- CreateProductCommandHandler: Gelen create product isteğinde aşağıdaki 'CreateProductCommandHandler' sınıfı devreye girecek ve 'CreateProduct' isimli metodu üzerinden aldığı 'CreateProductCommandRequest' nesnesindeki değerleri gerçek 'Product' nesnesine dönüştürerek create işlemini gerçekleştirecek ve ardından geriye 'CreateProductCommandResponse' dönerek kullanıcıyı bilgilendirecektir.
- DeleteProductCommandHandler: Gelen delete product isteğinde aşağıdaki 'DeleteProductCommandHandler' sınıfı devreye girecek ve 'DeleteProduct' isimli metodu üzerinden aldığı 'DeleteProductCommandRequest' nesnesindeki 'Id' değerine karşılık 'Product' nesnesini elde ederek kaynaktan silecek ve ardından geriye 'DeleteProductCommandResponse' dönerek kullanıcıyı bilgilendirecektir.
- GetAllProductQueryHandler: Gelen product listesi isteğinde aşağıdaki 'GetAllProductQueryHandler' sınıfı devreye girecek ve 'GetAllProduct' isimli metodu üzerinden tüm 'Product'ları çekecek ve 'List' nesnesine dönüştürüp kullanıcıya döndürecektir. Burada 'GetAllProductQueryRequest' nesnesi request olarak gelecektir fakat herhangi bir şarta vs. bağlı bir operasyon gerçekleştirilmeyeceği için içi boş olarak tasarlandığından dolayı herhangi bir işlevsellik göstermemektedir.
- GetByIdProductQueryHandler: Id bazlı product isteğinde ise aşağıdaki 'GetByIdProductQueryHandler' sınıfı devreye girecek ve 'GetByIdProduct' isimli metodu üzerinden gelen 'GetByIdProductQueryRequest' nesnesindeki 'Id' değerine karşılık 'Product' nesnesi ayıklanıp, 'GetByIdProductQueryResponse' nesnesine dönüştürülüp geriye gönderilecektir.
İşte CQRS tasarımı bu şekilde ve bundan ibarettir. Artık kullanımına geçelim.
- Benim uygulamam Asp.NET Core uygulaması üzerinden örneklendirme olduğu için ilgili Handler sınıflarını uygulamama servis olarak ekliyorum.
Daha sonra 'ProductsController' isminde bir controller oluşturup içerisinde aşağıdaki gibi endpointlerimi dolduruyorum.
Uygulamamı ayağa kaldırıp ve test ediyorum.
Gördüğünüz üzere CQRS pattern'i bu şekilde manuel olarak tasarlarsak eğer ister istemez hem çok zahmetli bir inşa sürecinde bulunmamız gerekecek hem de controller sınıfında haddinden fazla inject işlemi yapmamız gerekecektir. Nihayetinde hangi Response sınıfının hangi Request sınıfına ait olduğunu ve hangi Handler tarafından işleneceğini irademizle takip etmek mecburiyetindeyiz.
İşte buradaki çoğul model yönetimini daha dinamik bir şekilde sağlayabilmek için Mediator pattern'inden faydalanabiliriz.
Mediator design patternini birbirleriyle ilişkili eş görevli bir grup nesneyi tek merkezden yönetmek ve aralarında gevşek bağlı(loosely coupled) sistemler kurmak istediğimiz durumlarda kullanırız. Mediator patterninden faydalanmak için MediatR Kütüphanesini kullanabiliriz.
Öncelikle uygulamama MediatR Kütüphanesi eşliğinde Asp.NET Core projesi olmasından dolayı dependency injection paketi olan MediatR.Extensions.Microsoft.DependencyInjection kütüphanelerinide yüklemeyi unutmuyorum.
Daha sonra Asp.NET Core uygulamama aşağıdaki gibi MediatR servisini ekliyorum.
- IRequest, command veya query requestlerini karşılayacak olan sınıflar tarafından implemente edilecek olan bir arayüzdür.
http://www.kamilgrzybek.com/tag/mediatr/
- IRequestHandler, command veya query requestlerinin işlenmesini sağlayacak olan Handler sınıflarının arayüzüdür. Generic olarak request ve response sınıflarının bildirilmesini ve ilgili sınıfa içerisindeki 'Handle' isimli methodu implemente etmemizi ister.
http://www.kamilgrzybek.com/tag/mediatr/
- CreateProductCommandRequest ve CreateProductCommandResponse class'larımız
- DeleteProductCommandRequest ve DeleteProductCommandResponse class'larımız
- GetAllProductQueryRequest ve GetAllProductQueryResponse class'larımız
- GetByIdProductQueryRequest ve GetByIdProductQueryResponse class'larımız
- CreateProductCommandHandler class'ımız
- DeleteProductCommandHandler class'ımız
- GetAllProductQueryHandler class'ımız
- GetByIdProductQueryHandler class'ımız
MediatR kütüphanesinin en can alıcı noktasıda bu command ve query sınıflarını controller üzerinde kullanırken oldukça kolay ve sade bir implementasyon gerektirmesidir. Örneği aşağıdaki görselde 😊