ÇSTech
Published in

ÇSTech

Mediator & CQRS Design Patterns and MediatR Library

Merhaba,
Bugün 3 farklı konuyu tek yazıda incelemeye çalışacağız.

Bu konular yazının başlığından da anlaşılacağı gibi;
1- Mediator Design Pattern
2- CQRS (Command Query Responsibility Segregation)
3- MediatR Library

Neden bu üç ayrı konuya tek yazıda değindiğimi sorabilirsiniz.

Hemen bu konuda bir açıklama yapayım. İlk iki konu birbirinden ayrı olsalar da aslında 3. konumuz olan MediatR kütüphanesi bu iki konunun uygulanabilmesi için yazılmış bir kütüphanedir. Bu yüzden önce bu konuları anlayıp sonra da MediatR kütüphanesini birlikte inceleyeceğiz.

Mediator Design Pattern

Mediator Design Pattern; mediator kelime anlamı olarak ara bulucu demektir.

Yani aslında yapıların birbirine bağımlılığının düşük şekilde olduğu (Loose Coupling) yapılar inşa etmemizi amaçlar. Aynı arayüz üzerinden türeyen nesneler arasındaki iletişimi, tek bir nokta üzerinden sağlamaya dayalı bir pattern’dir. İletişimi tek bir sınıf (Mediator) üzerinden sağladığı için gevşek bağlılık sağlar.

Tam anlamıyla zihnimizde canlandırmak için hayatın içinden birkaç örnek vererek açıklamaya çalışalım.

Uçak - Kule - Pist:

Mediator Pattern konusu anlatılırken en sık kullanılan örnek uçak, kule ve pist örneğidir. Haydi biz de bu örnek üzerinden Mediator Pattern’i anlamaya çalışalım.

Hepimizin bildiği üzere, uçaklar hangi piste ineceğine veya hangi pistten kalkış yapacağına kendi başlarına karar veremezler, buna karar veren hava trafik kontrol yani bilinen adı ile kuledir.

Çoğumuz yaşamışızdır, uçakla gideceğimiz yere varırız ve havalimanını görsek bile bazen dakikalarca iniş yapamayız veya uçak kalkarken uçağın içinde dakikalarca kalkış yapmasını bekleriz.

İşte burada uçaklar hangi piste iniş yapabileceğini, hangi pistten kalkış yapabileceğini kuleye sorar ve kuleden cevap beklerler. Hangi pist ne zaman kullanılabilir olacak, hangi uçak hangi pisti ne zaman kullanabilecek, işte bu gibi durumlara kule karar verir. Uçakların, kulenin neye göre nasıl karar verdiğini, arka planda nasıl bir karar mekanizması olduğunu bilmelerine gerek yoktur. Uçaklar kuleye sorarlar ve onun yönlendirmesine göre iniş veya kalkış yapacağı pisti öğrenirler.

İşte bu senaryodaki KULE bizim aracımız yani Mediator’ümüzdür.

Aslında konuyu zihnimizde canlandırınca, yazılımsal olarak aklımıza gelen ilk örnek sanırım many-to-many ilişkili veri tabanındaki tablolar olacaktır.

Müşteri ve Ürün tabloları olduğunu düşünelim. Bir müşteri birden fazla ürün satın alabilir, bir ürünü birden fazla müşteri de satın alabilir. Aralarında many-to-many bir ilişki bulunuyor. Biz bu ilişkileri yönetmek için bir ara tablo oluşturuyoruz. Bu oluşturduğumuz ara tablolar ile aslında minik bir Mediator Pattern uygulamış oluyoruz.

CQRS (Command Query Responsibility Segregation) Design Pattern

Günümüzde popüler olan Design Patternlerin çoğu hayatımızda yeni olsalar da aslında çok daha önce ortaya atılmış kavramlardır. Yeni popüler hale gelmelerinin sebebi ise aslında geliştirme süreçlerimizin, live çıkış süreçlerimizin ve tasarladığımız sistemlerin bu patternleri daha kolay uygulanabilir hale getirmesidir.

Tıpkı yukarıda bahsettiğim gibi CQS (Command–Query Separation) “yanlış yazmadım merak etmeyin” 1988 yılında Bertrand Meyer’in ortaya attığı bir kavramdır. Ana amacı command ve queryleri (Command ve querylerin ne olduğuna ileride değineceğiz.) interfaceler aracılığı ile birbirinden ayırmaktı.

2005 yılında Martin Fowler CQS yaklaşımının çeşitli eksiklikleri olduğundan bahsetti ve 2010 yılında Greg Young CQRS pattern’ini ortaya attı. CQS’den farklı olarak command ve queryleri sadece interface olarak değil business olarak da birbirinden ayırmanın gerekliliklerinden bahsetti ve CQRS kullanarak belirli konularda başarı sağlanabileceğini öne sürdü.

Bu konular;
1- Clean Code (Temiz ve düzenli kodlama)
2- Performance (Performans)
3- Scalability (Ölçeklenebilirlik)
4- Traceability (Takip edilebilirlik)

Şimdi CQRS’in ne olduğunu ve bunun yukarıda saydığımız başlıklarda nasıl fayda sağladığını inceleyelim.
CQRS’i üç aşamadan daha küçük bir yapıdan genişleterek inceleyeceğiz.

Nedir Bu CQRS?
CQRS yukarıda da bahsettiğimiz gibi aslında command ve querylerin business olarak birbirinden ayrılmasını amaçlar. Peki command ve query nedir?

Klasik Sistem

Command: Verinin manipülasyona uğradığı veya state (durum) değişikliğine uğradığı işlemlerdir. Yani Insert, Update, Delete işlemleri bizim için command işlemlerdir.

Command işlemlerinde geriye veri dönülmemesi esastır.

Query: Verinin herhangi bir değişikliğe uğramadığı, veri çektiğimiz yani GET yaptığımız işlemlerdir.

CQRS’i 3 aşamada genişleterek ele alacağımızdan bahsetmiştik. Aşamaları aşağıdaki başlıklarda sırayla inceleyelim.

1. Aşama: Bu yapıda sağladığı avantajlardan biri olan clean code kısmını sağlamış oluyoruz. Çünkü artık command ve query kodlarını birbirinden ayırdık. Ayrı endpointler, ayrı business ve ayrı modellerle daha düzenli bir hale getirmiş olduk.

Peki ama peki diğer avantajlar? Bunları diğer aşamalardan elde edeceğiz.

1. Aşama CQRS Sistemi

2. Aşama: Bir sistemde command işlemleri ile query işlemlerinin oranı 3/7 gibi büyük oranda bir fark barındırır. Bizim her 10 requestimizden 3’ü command yani Insert, Update, Delete işlemi olurken geri kalan 7 işlem ise read yani veriyi okuduğumuz işlemlerdir.

2. Aşama CQRS Sistemi

Anlaşıldığı üzere günümüzde biz daha çok veri okuyan sistemler inşa ediyoruz. Peki durum bu şekilde iken yukarıdaki yapıdaki gibi command ve queryleri aynı API’de barındırırsak yapıyı doğru ölçekleyebilir miyiz?

Cevap tabii ki hayır.

Çünkü bizim 10 isteğimizin tamamı aynı API’ye gelecek. Bizim aslında asıl yoğunluğumuz olan queryler iken biz gereksiz bir şekilde commandları da scale etmiş olacağız.

Command ve queryleri ayrı API’lerde yazarsak, bizim command ve querylerimiz doğru bir şekilde ölçeklenebilir, hatta isteğe göre farklı makinelar kullanarak performansını da geliştirebiliriz.

Bu şekilde aslında 2. aşamayla birlikte 1. aşamada elde ettiğimiz Clean Code yapısının yanına Scalability, Traceability kısımlarını da ekleyerek daha verimli bir sistem inşa etmiş olduk.

3. Aşama: Önceki aşamalarla birlikte sistemimize Clean Code, Scalability, Traceability gibi özellikler kazandırmış olduk. Peki Performance? İşte bunun için yapımızı bir adım daha ileriye taşımamız gerekiyor. O adım da okuduğumuz ve yazdığımız databaseleri birbirinden ayırmak.

3. Aşama CQRS Sistemi

Databaseleri ayırmaya neden ihtiyaç duyalım dediğinizi duyar gibiyim. Şimdi bu konuyu birkaç başlıkta inceleyelim.

Farklı Özelliklerde Database Kullanmaya İhtiyaç Duyulması
Yazının başında da değindiğim bir konu vardı, veriyi okumaya duyulan ihtiyaç yazmaktan daha fazla diye. İşte bu durumda veriyi okumak için Elasticsearch gibi okuma ve arama için daha hızlı bir NOSQL database çözümüne giderken, yazmak için MSSQL veya MYSQL gibi Relational Database çözümlerine gidebiliriz. Bu da bize performans anlamında bir kazanım sağlar.

Ölçeklenebilir Database Kullanmaya İhtiyaç Duyulması
Burada da gelen isteklerin yoğunluğuna göre bütün database’i genişletmek yerine databaseleri ayırarak gerçekten ihtiyacımız olanı genişletebilme imkânı buluruz.

Üç aşamada da CQRS nedir ve kullandığımızda sisteme sağlayacağı avantajlar nelerdir bunlardan bahsettik. Peki dezavantajları neler? Tabii ki yazılım dünyasında bir yaklaşım size belirli noktalarda avantaj ve kazanımlar sağlıyorsa, belirli noktalarda da kayıp ve dezavantajları mevcuttur. Kullanırken bunları da bilmek gerekir.

CQRS’in Dezavantajları Nelerdir?
1- Databaseleri ayırdığımızda veri tutarlılığını sağlamak için ekstra önlemler almamız gerekecektir.
2- Yapıyı giderek kompleks hale getirdiğimizde yönetmekte zorlanabiliriz.
3- Command ve queryleri ayırdığınızda kod tekrarına sebebiyet verebilir.

MediatR Library

Bu başlıkta yukarıda bahsettiğimiz CQRS ve Mediator Patternlerinin MediatR Kütüphanesi aracılığı ile nasıl uygulandığını inceleyeceğiz.

Başlamadan önce MediatR kütüphanesi içerisinde kullanacağımız bazı önemli kavramlardan bahsetmeliyim.

  • Handler: Command ve querylerin businesslerini barındıran ve bizim asıl işleri yaptığımız kısım handlerlardır.
  • Pipeline Behaviour: Requestler ve handlerlar arasına girerek log, validasyon gibi işlemleri yapmamızı sağlayan kısımdır.
  • Send: Yazının ilk başında bahsettiğimiz Mediatörümüz aslında burada sender rolündedir. Gelen requestlerle handlerler arasındaki bağlantıyı sağlayarak bizim handlerlere doğrudan erişimimizi önler. Kısaca Requestler ve handlerlar arasında aracılık yapar.
  • Publish: Publish send den farklı olarak sonuç dönmeyeceğimiz asenkron işlemler için yani sms mail veya bi işlem yapıldığında event atma gibi aynı anda bir kaç handlere gitmesi gereken işler için kullanılır.

Pekala, kütüphanede kullanacağımız yapılara değindiğimize göre artık kod kısmına geçebiliriz.

Önce projemize aşağıdaki kütüphaneleri indiriyoruz.

Install-Package MediatR -Version 9.0.0
Install-Package MediatR.Extensions.Microsoft.DependencyInjection -Version 9.0.0

Ardından aşağıdaki 1. kod satırını builderdan önce Program.cs dosyasına ekliyoruz.

MediatR kütüphanesini kullanarak bir kullanıcının bilgilerini çekeceğimiz basit bir API yapalım.

Öncelikle query modelimizi oluşturalım. Query modelimiz aslında bizim handlerımızın çalışması için gerekli olan request verilerini barındırmalıdır.

QueryModelimiz IRequest interface’inden kalıtılmalı ve generic class olarak da handlerdan dönecek response model verilmelidir.

Ardından handlerımızı yazıyoruz. Handler’ı oluştururken onu da IRequestHandler interface’inden kalıtıyoruz.

IRequestHandler’ın aldığı ilk generic class RequestModel ikincisi ise dönen ResponseModel olmalıdır.

Oluşturduğumuz GetCustomerByIdHandler classında IRequestHandler dan gelen ve bizim GetCustomerByIdHandler içerisinde implemente etmemiz gereken Handle adında bir metot vardır. Yani controllerdan direkt request handle metoduna düşecektir.

Customer Service ise dummy verileri çektiğimiz bir servistir.

Şimdi son adım olan controllerımızı yazalım.
Aşağıdaki kod bloğunda olduğu gibi önce IMediator interface’ini inject ediyoruz.
Send metodunu kullanarak içerisine GetCustomerByIdQuery modelini parametre olarak veriyoruz.

Görüldüğü gibi tek bağımlılığımız Send metoduna, onun dışında arkadaki hiçbir handlera bağımlılığımız yok ve içerisindeki businessların da nasıl çalıştığını bizim için önemsiz.
Bu şekilde yazının başında bahsettiğim Mediator Pattern’i kolaylıkla uygulamış oluyoruz.

Aynı zamanda her endpoint için ayrı handler yazdığımız request ve response modelleri de ayırdığımız zaman, birbirinden bağımsız ve iş parçalarına bölünmüş bir yapı elde ediyoruz.

Böylece CQRS tarafınıda bu şekilde basit bir yapıyla uygulamış oluyoruz.

MediatR Notification Yapısı

MediatR’ın diğer bir yeteneği ise asenkron event yapılarıdır.

Şöyle bir senaryo oluşturalım; kullanıcı şifre değiştirdiğinde, bununla ilgili mail attığımızı düşünelim. Şifre değişiklik işlemi yapıldıktan sonra aslında işlem tamamlanmış oluyor ve mail atılması için kullanıcıyı bekletmek istemiyoruz. İşte bu tür durumlarda Notification yapısını kullanarak bu durumu çözebiliriz.

Bunu kod örnekleri ile inceleyelim.

Öncelikle CustomerPasswordChangedEvent adında INotification request model oluşturuyoruz.

CustomerPasswordChangedEventHandler adında bir Handler oluşturuyoruz ve bunu INotificationHandler<CustomerPasswordChangedEvent> dan miras aldırıyoruz. Generic olarakta az önce oluşturduğumuz CustomerPasswordChangedEvent modelimizi veriyoruz. Ardından Handle metodumuzu yazıyoruz.

Bu CustomerPasswordChangedEvent modelini kullanarak herhangi bir yerden publish yapılan bütün istekler bizim Handle metodumuza düşecek.

Senaryomuzda şifre değişikliği olduğundan bununla ilgili de CustomerPasswordChangeHandler adında bir handler oluşturup bu işlem tamamlandığında da constructor injection ile eklediğimiz IPublisher üzerinden .Publish diyerek CustomerPasswordChangedEvent modelimizi kullandığımız zaman yukarıdaki CustomerPasswordChangedEventHandler içerisinde bu isteği yönlendirmiş oluyoruz.

Bu şekilde bir event yapısı oluşturup kullanıcıyı bekletmeden asenkron işlemler yürütebiliriz.

MediatR Pipeline Behavior Yapısı

Son başlığımız da Pipeline Behavior. Yukarıda kısaca bahsetmiştik aslında Pipeline Behavior, süreç devam ederken araya girerek log, validasyon vb. gibi işlemleri yapmamızı sağlayan bir süreç olarak düşünebiliriz.

Peki bunu nasıl yapıyoruz? Hadi bir kod örneği üzerinden inceleyelim.

Gelen her requesti logladığımız bir senaryo düşünelim. LoggingBehaviour adında IPipelineBehavior’den türeyen bir class oluşturuyoruz. Buradaki Generic kısımları genel yaparsak hepsini loglar ama sadece belirli bir iş yapılarken loglama yapmak istersek buradaki alanlara doğrudan bir request ve response model verilebilir.

Yazdığımız LoggingBehaviour kısmınıda aşağıdaki gibi startup veya program.cs de register etmemiz gerekiyor.

Böylece bu son konuyu da tamamlayarak yazımızın sonuna geliyoruz.

Umarım herkes için keyifli ve bilgilendirici bir yazı olmuştur.

Demo projeye aşağıdaki linkten ulaşabilirsiniz.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store