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

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

Bir cevap yazın

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

*