Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

CQRS Pattern Nedir? MediatR Kütüphanesi İle Nasıl Uygulanır?

Merhaba,

Bu içeriğimizde son zamanlarda sıkça konuşulan CQRS Pattern üzerine istişare eyliyor olacağız. CQRS nedir, nasıl uygulanır? sorularının yanında ayrıca MediatR kütüphanesi ile de nasıl uyum sağladığını ve uygulandığını değerlendiriyor olacağız. Haydi gelin vakit kaybetmeden başlayalım…

CQRS Nedir?

İlk olarak kavram tanımlaması yaparak mevzuya başlayalım. CQRS, özünde Command Query Responsibility Segregation‘ın kısaltılmış halidir. Açılımından da anlaşılacağı üzere ‘Command’ ve ‘Query’ sorumluluklarının ayrılması prensibini esas alan bir yaklaşımı savunmaktadır. Şimdi diyeceksiniz ki, -la hoca ‘Command’ neee, ‘Query’ neee?-

Biliyorsunuz ki, bir uygulama üzerinde kullanıcıdan gelen istekler iki türlüdür. Gelen istek, ya mevcudiyetteki bir veri üzerinde manipülasyon/değişiklik 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 uzun lafın kısası, gelen istek ya bir salt read/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.
    INSERT UPDATE DELETE
  • Query
  • Mevcut verileri sadece listelemek, okumak yahut sunmak için read işlemi yapan isteklerdir.
    SELECT

İşte CQRS, uygulamalarımızda bu istekleri karşılayacak olan yapılanmaları birbirinden ayırmamızı önermektedir.

Peki hoca! Neden ayırmamızı önermekte? Ayırmayınca ne oluyor? Ayırınca artısı ne olacak?
Güzel soru… Gelin bunu şöyle örnek bir olay üzerinden cevaplandırmaya çalışalım. Varsayalım ki, bir web uygulaması geliştiriyorsunuz ve kullanıcı ürün eklemek ve listelemek istiyor. Böyle bir ihtiyaç için aşağıdaki farazi tasarımı yaptığınızı düşünelim…

‘Product’ isimli bir entity oluşturdunuz.

    public class Product
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public int Quantity { get; set; }
        public decimal Price { get; set; }
        public DateTime CreateTime { get; set; }
    }

Haliyle bu entity’i direkt dış dünyaya açmamak için create ve read operasyonlarına karşılık ViewModel nesnelerini tasarladığınız.

    public class ProductReadVM
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public DateTime CreateTime { get; set; }
    }
    public class ProductCreateVM
    {
        public string Name { get; set; }
        public int Quantity { get; set; }
        public decimal Price { get; set; }
    }

Gelen isteğe göre eldeki verileri uygun view model nesneleriyle geriye döndüren servis oluşturdunuz.

    public class ProductService
    {
        public List<ProductReadVM> GetProducts()
        {
            var productList = ApplicationDbContext.ProductList;
            return productList.Select(product => new ProductReadVM
            {
                Id = product.Id,
                Name = product.Name,
                CreateTime = product.CreateTime
            }).ToList();
        }

        public void CreateProduct(ProductCreateVM product)
        {
            var productModel = new Product
            {
                Name = product.Name,
                Price = product.Price,
                Quantity = product.Quantity
            };
            ApplicationDbContext.ProductList.Add(productModel);
        }
    }

İşte burada ilgili servis dataları entity türünde elde edip, ilgili view model türüne dönüştürme sürecinde oldukça kompleks bir domain logic uygulamakta ve iş kuralı getirmektedir. Bu basit ölçekli uygulamalarda göz ardı edilebilir bir durumken, gelişen ve gelişmeyi vaat eden uygulamalar da oldukça risk teşkil eden bir durumdur. Hele hele bu tarz bir tasarımda Generic Repository tasarım deseni uygulandığını düşünürseniz işin içinden çıkılması oldukça zahmetli olacaktır.

CQRS, karmaşık hale gelen Object Mapping işlemlerini oldukça kolaylaştırmaktadır.

CQRS Pattern Uygulaması

CQRS Pattern Nedir? MediatR Kütüphanesi İle Nasıl Uygulanır?
CQRS, verileri güncellemek için ‘Command’ sınıflarını, okumak için ise ‘Query’ sınıflarını kullanmaktadır. Tabi burada öncelikle CQRS pattern’ının tasarımını ele alacağız. Ardından bu tasarımı daha hızlı ve dinamik bir şekilde uygulamamızı sağlayacak olan MediatR kütüphanesiyle kurmaya çalışacağız.

CQRS Pattern Nedir MediatR Kütüphanesi İle Nasıl Uygulanırİlk olarak ilgili uygulama ya da katman içerisinde ‘CQRS’ isminde bir klasör oluşturunuz. Ardından içerisine ‘Commands’, ‘Queries’ ve ‘Handlers’ isimlerinde klasörler oluşturunuz. Ardından tüm klasörlerin içerisine yandaki 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şturunuz. Burada hoca Handlers klasörü ne amaca hizmet ediyor? şeklindeki sorunuzu duyar gibiyim… Ben yine de cevaplandırmayı tüm klasörleri izah ederek yapmaya çalışayım.

  • Commands
    Uygulamada yapılacak olan tüm Command’leri 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.
  • Queries
    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.
  • Handlers
    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 gelin Command ve Query sınıflarını oluşturalım.

Commands
  • CreateProductCommandRequest & CreateProductCommandResponse
    Product ekleme isteklerinde kullanılacaktır.

    namespace DAL.CQRS.Commands.Request
    {
        public class CreateProductCommandRequest
        {
            public string Name { get; set; }
            public int Quantity { get; set; }
            public decimal Price { get; set; }
        }
    }
    
    namespace DAL.CQRS.Commands.Response
    {
        public class CreateProductCommandResponse
        {
            public bool IsSuccess { get; set; }
            public Guid ProductId { get; set; }
        }
    }
    
  • DeleteProductCommandRequest & DeleteProductCommandResponse
    Product silme isteklerinde kullanılacaktır.

    namespace DAL.CQRS.Commands.Request
    {
        public class DeleteProductCommandRequest
        {
            public Guid Id { get; set; }
        }
    }
    
    namespace DAL.CQRS.Commands.Response
    {
        public class DeleteProductCommandResponse
        {
            public bool IsSuccess { get; set; }
        }
    }
    
Queries
  • GetAllProductQueryRequest & GetAllProductQueryResponse
    Tüm product verileri elde edilmek istendiğinde kullanılacaktır.

    namespace DAL.CQRS.Queries.Request
    {
        public class GetAllProductQueryRequest
        {
    
        }
    }
    
    namespace DAL.CQRS.Queries.Response
    {
        public class GetAllProductQueryResponse
        {
            public Guid Id { get; set; }
            public string Name { get; set; }
            public int Quantity { get; set; }
            public decimal Price { get; set; }
            public DateTime CreateTime { get; set; }
        }
    }
    
  • GetByIdProductQueryRequest & GetByIdProductQueryResponse
    Id bazlı product sorgulamalarında kullanılacaktır.

    namespace DAL.CQRS.Queries.Request
    {
        public class GetByIdProductQueryRequest
        {
            public Guid Id { get; set; }
        }
    }
    
    namespace DAL.CQRS.Queries.Response
    {
        public class GetByIdProductQueryResponse
        {
            public Guid Id { get; set; }
            public string Name { get; set; }
            public int Quantity { get; set; }
            public decimal Price { get; set; }
            public DateTime CreateTime { get; set; }
        }
    }
    
Handlers

Artık gelen Query yahut Command isteklerini işleyebiliriz.

  • 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.

    namespace DAL.CQRS.Handlers.CommandHandlers
    {
        public class CreateProductCommandHandler
        {
            public CreateProductCommandResponse CreateProduct(CreateProductCommandRequest createProductCommandRequest)
            {
                var id = Guid.NewGuid();
                ApplicationDbContext.ProductList.Add(new()
                {
                    Id = id,
                    Name = createProductCommandRequest.Name,
                    Price = createProductCommandRequest.Price,
                    Quantity = createProductCommandRequest.Quantity,
                    CreateTime = DateTime.Now
                });
                return new CreateProductCommandResponse
                {
                    IsSuccess = true,
                    ProductId = id
                };
            }
        }
    }
    
  • 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(veritabanı vs.) silecek ve ardından geriye ‘DeleteProductCommandResponse’ dönerek kullanıcıyı bilgilendirecektir.

    namespace DAL.CQRS.Handlers.CommandHandlers
    {
        public class DeleteProductCommandHandler
        {
            public DeleteProductCommandResponse DeleteProduct(DeleteProductCommandRequest deleteProductCommandRequest)
            {
                var deleteProduct = ApplicationDbContext.ProductList.FirstOrDefault(p => p.Id == deleteProductCommandRequest.Id);
                ApplicationDbContext.ProductList.Remove(deleteProduct);
                return new DeleteProductCommandResponse
                {
                    IsSuccess = true
                };
            }
        }
    }
    
  • 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<GetAllProductQueryResponse>’ nesnesine dönüştürüp kullanıcıya döndürecektir. Burada ‘GetAllProductQueryRequest’ nesnesi request olarak gelecektir lakin 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.

    namespace DAL.CQRS.Handlers.QueryHandlers
    {
        public class GetAllProductQueryHandler
        {
            public List<GetAllProductQueryResponse> GetAllProduct(GetAllProductQueryRequest getAllProductQueryRequest)
            {
                return ApplicationDbContext.ProductList.Select(product => new GetAllProductQueryResponse
                {
                    Id = product.Id,
                    Name = product.Name,
                    Price = product.Price,
                    Quantity = product.Quantity,
                    CreateTime = product.CreateTime
                }).ToList();
            }
        }
    }
    
  • 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.

    namespace DAL.CQRS.Handlers.QueryHandlers
    {
        public class GetByIdProductQueryHandler
        {
            public GetByIdProductQueryResponse GetByIdProduct(GetByIdProductQueryRequest getByIdProductQueryRequest)
            {
                var product = ApplicationDbContext.ProductList.FirstOrDefault(p => p.Id == getByIdProductQueryRequest.Id);
                return new GetByIdProductQueryResponse
                {
                    Id = product.Id,
                    Name = product.Name,
                    Price = product.Price,
                    Quantity = product.Quantity,
                    CreateTime = product.CreateTime
                };
            }
        }
    }
    

CQRS Pattern Nedir MediatR Kütüphanesi İle Nasıl Uygulanır

Son durum…


İşte salt bir şekilde CQRS tasarımı bu şekilde ve bundan ibarettir. Artık kullanımına geçebiliriz.

Bizler, Asp.NET Core uygulaması üzerinden örneklendirme yaptığımız için ilgili Handler sınıflarını uygulamaya servis olarak ekliyoruz.

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<CreateProductCommandHandler>();
            services.AddTransient<DeleteProductCommandHandler>();
            services.AddTransient<GetAllProductQueryHandler>();
            services.AddTransient<GetByIdProductQueryHandler>();
            .
            .
            .
        }
        .
        .
        .

Ardından ‘ProductController’ isminde bir controller açıp içerisinde aşağıdaki gibi çalışma yapılması yeterli olacaktır.

    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
        CreateProductCommandHandler _createProductCommandHandler;
        DeleteProductCommandHandler _deleteProductCommandHandler;
        GetAllProductQueryHandler _getAllProductQueryHandler;
        GetByIdProductQueryHandler _getByIdProductQueryHandler;
        public ProductController(
            CreateProductCommandHandler createProductCommandHandler,
            DeleteProductCommandHandler deleteProductCommandHandler,
            GetAllProductQueryHandler getAllProductQueryHandler,
            GetByIdProductQueryHandler getByIdProductQueryHandler)
        {
            _createProductCommandHandler = createProductCommandHandler;
            _deleteProductCommandHandler = deleteProductCommandHandler;
            _getAllProductQueryHandler = getAllProductQueryHandler;
            _getByIdProductQueryHandler = getByIdProductQueryHandler;
        }

        [HttpGet]
        public IActionResult Get([FromQuery] GetAllProductQueryRequest requestModel)
        {
            List<GetAllProductQueryResponse> allProducts = _getAllProductQueryHandler.GetAllProduct(requestModel);
            return Ok(allProducts);
        }

        [HttpGet("{id}")]
        public IActionResult Get([FromQuery] GetByIdProductQueryRequest requestModel)
        {
            GetByIdProductQueryResponse product = _getByIdProductQueryHandler.GetByIdProduct(requestModel);
            return Ok(product);
        }

        [HttpPost]
        public IActionResult Post([FromBody] CreateProductCommandRequest requestModel)
        {
            CreateProductCommandResponse response = _createProductCommandHandler.CreateProduct(requestModel);
            return Ok(response);
        }

        [HttpDelete("{id}")]
        public IActionResult Delete([FromQuery] DeleteProductCommandRequest requestModel)
        {
            DeleteProductCommandResponse response = _deleteProductCommandHandler.DeleteProduct(requestModel);
            return Ok(response);
        }
    }

Uygulamayı derleyip, ayağa kaldıralım ve test edelim.
CQRS Pattern Nedir? MediatR Kütüphanesi İle Nasıl Uygulanır?

Görüldüğü üzere CQRS pattern’ı 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. Ayrıyeten buradaki sınıfların yönetimi oldukça zor ve zahmetlidir. 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. Tabi burada kodun üretkenliği ve okunabilirliği proje ilerledikçe ve karmaşıklığı arttıkça ayrı bir yük haline geldiği ve geleceği de aşikardır.

CQRS, okuma ve yazma iş yüklerinin bağımsız olarak ölçeklendirilebilmesine olanak tanır.

İşte buradaki çoğul model yönetimini daha dinamik bir şekilde sağlayabilmek ve kodumuzu daha pratik geliştirilebilir bir hale getirebilmek için Mediator pattern’ından faydalanabiliriz.

Mediator Pattern Nedir?

Mediator pattern, hani şu herkesin uçak senaryosu üzerinden örneklendirmeye çalıştığı tasarım desenidir. Tabi ben burada Mediator ile ilgili geniş bilgi edinebilmeniz için konuya dair detaylı ve bol örnekli bir şekilde kaleme aldığım Mediator Design Pattern başlıklı makalemi referans göstermek istiyorum. Yinede kısaca ne olduğunu özetlememiz gerekirse eğer,

Mediator, tek bir aracı(mediator) nesnesi içerisinde çeşitli nesneler arasındaki karmaşık ilişkiler ağını yönetmenize olanak tanıyan bir tasarım desenidir.

şeklinde bir açıklama yeterli olacaktır kanaatindeyim. Yani yukarıda yaptığımız CQRS pattern’ındaki tüm request, response ve handle nesnelerini, davranışlarına göre yönetebilmemizi sağlayan merkezi bir yapılanma oluşturmak için Mediator pattern’dan faydalanabiliriz.

MediatR Kütüphanesi Nedir?

Mediator pattern’dan faydalanmak için kendimizce manuel bir tasarım yapabileceğimiz gibi bu pattern’ı benimseyen ve işlemleri daha hızlı ve efektif bir şekilde gerçekleştirmemizi sağlayacak olan MediatR Kütüphanesini‘de kullanabiliriz. Biz bu içeriğimizde MediatR kütüphanesinden istifade edecek ve konumuza bu nitelikle devam edeceğiz…

Öncelikle ilgili uygulamaya MediatR Kütüphanesi eşliğinde Asp.NET Core projesi olmasından dolayı dependency injection paketi olan MediatR.Extensions.Microsoft.DependencyInjection kütüphanelerini yükleyiniz.

Ardından Asp.NET Core uygulamasına aşağıdaki gibi MediatR servisini ekleyiniz.

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            //Burada Command ve Query'lerin oluşturulduğu projedeki herhangi bir classın verilmesi
            //kafidir.
            services.AddMediatR(typeof(ApplicationDbContext));
            .
            .
            .
        }
    }

Ardından Command, Query ve Handler sınıflarını aşağıdaki gibi bu sefer MediatR kütüphanesinin getirisi olan IRequest ve IRequestHandler arayüzleriyle tekrardan tanımlayalım.

IRequest IRequestHandler
CQRS Pattern Nedir MediatR Kütüphanesi İle Nasıl Uygulanır
IRequest, command yahut query requestlerini karşılayacak olan sınıflar tarafından implemente edilecek olan bir arayüzdür. Generic olarak bu request karşılığında hangi nesnenin döndürüleceğini bildirmemizi ister. IRequestHandler ise command yahut 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 ister ve ilgili sınıfa içerisindeki ‘Handle’ isimli fonksiyonu implemente ettirir.
Commands
  • CreateProductCommandRequest & CreateProductCommandResponse
    namespace DAL.CQRS.Commands.Request
    {
        public class CreateProductCommandRequest : IRequest<CreateProductCommandResponse>
        {
            public string Name { get; set; }
            public int Quantity { get; set; }
            public decimal Price { get; set; }
        }
    }
    
    namespace DAL.CQRS.Commands.Response
    {
        public class CreateProductCommandResponse
        {
            public bool IsSuccess { get; set; }
            public Guid ProductId { get; set; }
        }
    }
    
  • DeleteProductCommandRequest & DeleteProductCommandResponse
    namespace DAL.CQRS.Commands.Request
    {
        public class DeleteProductCommandRequest : IRequest<DeleteProductCommandResponse>
        {
            public Guid Id { get; set; }
        }
    }
    
    namespace DAL.CQRS.Commands.Response
    {
        public class DeleteProductCommandResponse
        {
            public bool IsSuccess { get; set; }
        }
    }
    
Queries
  • GetAllProductQueryRequest & GetAllProductQueryResponse
    namespace DAL.CQRS.Queries.Request
    {
        public class GetAllProductQueryRequest : IRequest<List<GetAllProductQueryResponse>>
        {
    
        }
    }
    
    namespace DAL.CQRS.Queries.Response
    {
        public class GetAllProductQueryResponse
        {
            public Guid Id { get; set; }
            public string Name { get; set; }
            public int Quantity { get; set; }
            public decimal Price { get; set; }
            public DateTime CreateTime { get; set; }
        }
    }
    
  • GetByIdProductQueryRequest & GetByIdProductQueryResponse
    namespace DAL.CQRS.Queries.Request
    {
        public class GetByIdProductQueryRequest : IRequest<GetByIdProductQueryResponse>
        {
            public Guid Id { get; set; }
        }
    }
    
    namespace DAL.CQRS.Queries.Response
    {
        public class GetByIdProductQueryResponse
        {
            public Guid Id { get; set; }
            public string Name { get; set; }
            public int Quantity { get; set; }
            public decimal Price { get; set; }
            public DateTime CreateTime { get; set; }
        }
    }
    
Handlers
  • CreateProductCommandHandler
    namespace DAL.CQRS.Handlers.CommandHandlers
    {
        public class CreateProductCommandHandler : IRequestHandler<CreateProductCommandRequest, CreateProductCommandResponse>
        {
            public async Task<CreateProductCommandResponse> Handle(CreateProductCommandRequest request, CancellationToken cancellationToken)
            {
                var id = Guid.NewGuid();
                ApplicationDbContext.ProductList.Add(new()
                {
                    Id = id,
                    Name = request.Name,
                    Price = request.Price,
                    Quantity = request.Quantity,
                    CreateTime = DateTime.Now
                });
                return new CreateProductCommandResponse
                {
                    IsSuccess = true,
                    ProductId = id
                };
            }
        }
    }
    
  • DeleteProductCommandHandler
    namespace DAL.CQRS.Handlers.CommandHandlers
    {
        public class DeleteProductCommandHandler : IRequestHandler<DeleteProductCommandRequest, DeleteProductCommandResponse>
        {
            public async Task<DeleteProductCommandResponse> Handle(DeleteProductCommandRequest request, CancellationToken cancellationToken)
            {
                var deleteProduct = ApplicationDbContext.ProductList.FirstOrDefault(p => p.Id == request.Id);
                ApplicationDbContext.ProductList.Remove(deleteProduct);
                return new DeleteProductCommandResponse
                {
                    IsSuccess = true
                };
            }
        }
    }
    
  • GetAllProductQueryHandler
    namespace DAL.CQRS.Handlers.QueryHandlers
    {
        public class GetAllProductQueryHandler : IRequestHandler<GetAllProductQueryRequest, List<GetAllProductQueryResponse>>
        {
            public async Task<List<GetAllProductQueryResponse>> Handle(GetAllProductQueryRequest request, CancellationToken cancellationToken)
            {
                return ApplicationDbContext.ProductList.Select(product => new GetAllProductQueryResponse
                {
                    Id = product.Id,
                    Name = product.Name,
                    Price = product.Price,
                    Quantity = product.Quantity,
                    CreateTime = product.CreateTime
                }).ToList();
            }
        }
    }
    
  • GetByIdProductQueryHandler
    namespace DAL.CQRS.Handlers.QueryHandlers
    {
        public class GetByIdProductQueryHandler : IRequestHandler<GetByIdProductQueryRequest, GetByIdProductQueryResponse>
        {
            public async Task<GetByIdProductQueryResponse> Handle(GetByIdProductQueryRequest request, CancellationToken cancellationToken)
            {
                var product = ApplicationDbContext.ProductList.FirstOrDefault(p => p.Id == request.Id);
                return new GetByIdProductQueryResponse
                {
                    Id = product.Id,
                    Name = product.Name,
                    Price = product.Price,
                    Quantity = product.Quantity,
                    CreateTime = product.CreateTime
                };
            }
        }
    }
    

MediatR kütüphanesinin en can alıcı noktası bu command ve query sınıflarını controller üzerinde kullanırken oldukça kolay ve sade bir implementasyon gerektirmesidir.
Şöyle ki;

    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
        IMediator _mediator;

        public ProductController(IMediator mediator)
        {
            _mediator = mediator;
        }

        [HttpGet]
        public async Task<IActionResult> Get([FromQuery] GetAllProductQueryRequest requestModel)
        {
            List<GetAllProductQueryResponse> allProducts = await _mediator.Send(requestModel);
            return Ok(allProducts);
        }

        [HttpGet("{id}")]
        public async Task<IActionResult> Get([FromQuery] GetByIdProductQueryRequest requestModel)
        {
            GetByIdProductQueryResponse product = await _mediator.Send(requestModel);
            return Ok(product);
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody] CreateProductCommandRequest requestModel)
        {
            CreateProductCommandResponse response = await _mediator.Send(requestModel);
            return Ok(response);
        }

        [HttpDelete("{id}")]
        public async Task<IActionResult> Delete([FromQuery] DeleteProductCommandRequest requestModel)
        {
            DeleteProductCommandResponse response = await _mediator.Send(requestModel);
            return Ok(response);
        }
    }

Görüldüğü üzere tüm implementasyon ‘IMediator’ referansı üzerinden kolayca gerçekleştirilebilmektedir.

Sizler ister manuel, isterseniz de MediatR kütüphanesi ile CQRS pattern’ını uygulayabilir ve uygulama üzerindeki isteklerin durumlarını kontrol edebilirsiniz.

Okuduğunuz için teşekkür ederim…

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Not : Örnek projeleri indirebilmek için lütfen aşağıdaki bağlantıları kullanınız.
CQRS Tasarımının Manuel Uygulanması(MediatR Kütüphanesi Kullanmadan)
CQRS Tasarımının MediatR Kütüphanesi İle Uygulanması

Bunlar da hoşunuza gidebilir...

24 Cevaplar

  1. qwe dedi ki:

    Hocam Selam,
    Öncelikle emeğinize sağlık.
    Query ve Command request-response nesnelerini ayrı bir katmana aktardığımda aşağıdaki hatayı almaktayım.
    services.AddMediatR(typeof(ApplicationDbContext)); => burada farklı ayar mı yapılmalı?

    Handler was not found for request of type MediatR.IRequestHandler

    • Gençay dedi ki:

      Merhaba,

      ‘AddMediatR’ servisine Command ve Query’lerin bulunduğu katman hangisiyse ondaki bir sınıfı vermeniz gerekmektedir. Handler sınıflarını bundan dolayı bulamıyor olabilir.

      • qwe dedi ki:

        Hocam Merhaba,
        Bilgilendirme için teşekkürler.
        Aslında o kotmandaki ilgili sınıfları verdim fakat hata alıyordum.
        namespace-assemblies çözemiyor diye anlıyorum.Aşağıdaki şekilde kullanınca düzeldi

        services.AddMediatR(AppDomain.CurrentDomain.GetAssemblies()); 
        

        Ayrıca izininzle yorumlarınızı rica ettiğim başka bir konu daha var 🙂
        QueryHandler nesneleri için her servis çağrısında bu senaryoda yeni bir instance oluşturmak best practice midir?

        Teşekkürler.

        • Gençay dedi ki:

          Merhaba,

          2. sorunuza dair cevabım “bilmiyorum” 🙂
          Nihayetinde CQRS pattern’da ben Handler sınıflarını MediatR ile kullandığım için bu durumun dışında sisteme dahil etmenin best practice olup olmadığını hiç değerlendirmedim 🙂

          Sevgiler.

  2. kral dedi ki:

    Merhaba Gençay Hocam,

    Hocam bloğunuz ve youtube kanalınız ile yaklaşık 1 ay önce tanıştım ve hayran kaldım. Çok değerli yazılarınız ve anlatımınız için minnettarım, selamlar..

  3. Selin dedi ki:

    Gayet açıklayıcı bir anlatımdı ve verdiğiniz örnek te gayet açıklayıcıydı konu havada kalmadı bu sayede. Yazı için teşekkürler.

  4. Şahin dedi ki:

    Hocam Merhaba ,

    Hocam büyük projelerde CQRS kullanmalı mıyız ya da hangi durumlarda kullanmamız önerilir.

    Saygılarımla
    Youtube : s0be

    • Gençay dedi ki:

      Merhaba,

      Özellikle microservice yaklaşımını kullandığınız uygulamalarda CQRS pattern’ı kullanmanız tavsiye edilir.

      İyi çalışmalar.

  5. oğuzhan Köcü dedi ki:

    Hocam merhaba . Benim sorum cqrs + mediatR ile ilgili yazmış olduğunuz blog yazınız ile ilgili. Uygulamasını az çok anladım ama bunun doğru uygulanış bakımından nasıl olduğiunu anlamadım çünkü yerden yere farklı şekilde bir uygulayışı olmuş. Siz handler sınıflarınızı ayrı ayrı oluşturmuşsunuz lakin bazı yerlerde bunu command veya query sınıflarının direk içinde oluşturulmuş halde uygulanmış. Bunu birkaç yerde görünce doğrusunun ne olduğu konusunda afalladım. Çünkü prensiplere de uymayan bir yaklaşım olduğunu düşünüyorum. Ben application aldı da cqrs klasörü içinde modellerimin tek tek klasörlerini oluşturup , onlarında içine command query ve handlerlarını oluşturup o şekilde devam etmeyi düşündüm . Fakat yine de bir servis oluşturmalı mıyım ve ilk başta bahsettiğim command sınıfı içinde handler sınıfı oluşturmak doğru mu ?

    • Gençay dedi ki:

      Merhaba,

      İlgili sınıfları farklı ya da aynı dosyalar ve namespaceler’de oluşturmanın teknik açıdan hiçbir farkı yoktur. Özünde bu durum erişim farkı yaratacaktır. İlgili sınıflara erişim için namespace tanımlaması(using) yapılması yeterli olacaktır.

  6. OguzC dedi ki:

    Öncelikle ellerinize sağlık benim için çok eğitici bir yazı oldu bunun için sizlere çok teşekkür ederim. Makalede anlayamadığım tek şey handle methodlarında parametre olarak aldığınız CancellationToken’lar hiç kullanılmamış onlar ne işe yarıyor acaba? Ve Request’i atarken bu token’ı nasıl parametre olarak vereceğiz? Cevabınız için şimdiden çok teşekkür ederim.

    • Gençay dedi ki:

      Merhaba,

      CancellationToken asenkron süreci durdurmamızı sağlayan bir tür. İçeriğimizdeki verilen örneklerde odağı dağıtmamak için kullanmadık. Ha neden tanımladınız o zaman diye sorarsanız ‘IReuqestHandler’ interface’i içerisinde tanımlanmış imzalarda olduğu için implemente etmek mecburiyetinde kaldık.

      Sevgiler.

  7. Zeynek dedi ki:

    Hocam merhabalar. Development ortamında her şey iyi güzel çalışıyor fakat projeyi publish edip production ortamına aldığımda yani host a attığımda mediatr dan dolayı hata veriyor. Hatayı araştırdığımda MediatR ın implementasyonundan kaynaklı bir hata olduğu ortaya çıkıyor. Service olarak eklemediğimi söylüyor. Fakat development da gayet güzel çalışıyor ve servislerde de ekli. Daha önce böyle bir sorunla karşılaştınız mı? Karşılaştıysanız eğer nasıl çözdünüz? Saygılar

    • Gençay dedi ki:

      Merhaba,

      Bahsettiğiniz soruna dair önceden bir tecrübe yaşamış değilim.

      Sevgiler.

      • Zeynel dedi ki:

        Sorunu log kayıtlarını inceleyince buldum hocam. Veritabanı bağlantısı kurulamadığı için böyle bir hata veriyor. Aynı sorunu yaşayan arkadaşlar olursa başta connection string olmak üzere repository bölümünü kontrol etmelerini öneririm.

  8. Mücahid dedi ki:

    Merhaba hocam,

    Örnekler Web projesi üzerinden verilmiş, peki bu MediatR yapısını windows console, windows form, windows servisleri gibi uygulamardada kullanabiliyor muyuz?

    Request istekleri bildiğim kadarıyla web uygulamalarında oluyor. Masaüstü uygulamalarında bu yaklaşım nasıl oluyor

  9. Samet dedi ki:

    Gençay bey merhaba,
    CQRS ve mediator desenlerini uygularken iş mantıklarımızı handler sınıfları içerisinde yapabilir miyiz, yoksa command handler sınıfları sadece DB işlerini yapmakla mı yükümlüler?
    ÖR: 2. bir servisten A nesnesi hakkında bilgi alıp, bu A nesnesi üzerinde mantıksal işlemler yapıp daha sonra command handler içerisinde veritabanına yazabilir miyiz?
    Bu gibi bir iş süreci olan bir uygulamada:
    1- 2.servis nerede çağırılmalı? Handler içerisinde mi yoksa onion arc içerisinde farklı bir yerde mi?
    2- A nesnesi üzerindeki mantıksal işlemler handler içerisinde mi yapılmalı, entity nesnesinin üzerinde mi, yoksa onion arc da farklı bir yerde mi?
    Kıymetli cevaplarınız için şimdiden teşekkür ederim.

    • Gençay dedi ki:

      Merhaba,

      Güzel soru. Eğer ki iş mantığı süreci doğrudan yahut dolaylı bir şekilde Command yahut Query ile ilgiliyse bunların handler sınıflarında çağrılması daha uygundur. Çünkü nihai olarak veritabanlarına yansıtılacak şüreçlerin topyekün hangi aşamalardan ve mantıklardan geçtiğine dair konsantre olunabilmesi ancak böylece mümkündür.

      Buna şöyle örnek verebiliriz;

      Bir dosya yükleme işlemini tahayyül edersek eğer dosyanın işlenmesi farklı bir servis tarafından, veritabanı işlemlerinin ise repository tarafından gerçekleştildiğini varsayalım. Eğer ki, gelen istek neticesinde bu command süreci söz konusuysa ‘AddFileCommandHandler’da uygun sırayla çağrılmaları ve tetiklenmeleri daha doğru olacaktır ki, bu handle işleminde hangi operasyonların yürütüldüğü bütünsel olarak daha net olsun.

      Umarım faydalı olmuştur.
      Kolaylıklar dilerim.

  1. 24 Mart 2021
  2. 20 Nisan 2021

    […] karşılık farklı bir veritabanından sonuç dönmemiz gerekecektir. İşte böyle bir durumda CQRS pattern‘ı Event Sourcing ile uygulayarak muhteşem ikiliyi sahneye sürebiliriz. Yandaki şemayı […]

  3. 30 Nisan 2021

    […] ayrımı üzerinden seyredecektir. Haliyle buradan anlaşılıyor ki, temel CQRS pattern eşliğinde basit bir Event Sourcing yaklaşımı […]

  4. 20 Mayıs 2021

    […] Esasında görüldüğü üzere Event Sourcing, CQRS pattern ile birlikte tam bir uyumluluk sergilemekte olan bir yapılandır. ‘Write Data Store’ […]

  5. 24 Eylül 2022

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir