🚀 .NET ile CQRS + MediatR Yapısını Kafaya Takmadan Öğren

CQRS ve MediatR kulağa karmaşık geliyor olabilir. Ama aslında doğru yerden bakarsan, gayet okunabilir ve sade bir yapı sunuyor.

Bu yazıda hiç veritabanına bulaşmadan, sadece bellekte (in-memory) bir ToDo listesi örneğiyle CQRS + MediatR nasıl kullanılır onu göstereceğim.


🧠 CQRS Nedir?

CQRS (Command Query Responsibility Segregation):
Yazma (Command) işlemleri ile okuma (Query) işlemlerini birbirinden ayırır. Bu sayede:

  • Kodun sorumlulukları ayrılır

  • Handler’lar sade olur

  • Okunabilirlik ve test kolaylığı artar

🧩 MediatR Nedir?

MediatR, .NET dünyasında kullanılan bir kütüphane. Amacı:

  • Controller’ın doğrudan servis çağırmaması

  • Onun yerine bir Request objesiyle işlem yapılması

Yani controller → request → MediatR → handler → işlem

🏗️ Proje Yapısı

Projeyi oluştur:

dotnet new webapi -n CqrsMediatRDemo
cd CqrsMediatRDemo
dotnet add package MediatR

📁 Klasör Yapısı

/CqrsMediatRDemo
│
├── Controllers/
│   └── ToDoController.cs
│
├── Domain/
│   ├── ToDo.cs
│   └── ToDoStore.cs
│
├── Application/
│   └── Features/
│       └── ToDo/
│           ├── CreateToDoCommand.cs
│           ├── CreateToDoHandler.cs
│           ├── GetAllToDosQuery.cs
│           └── GetAllToDosHandler.cs

⚙️ Program.cs ayarları

builder.Services.AddMediatR(x => x.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));

🧩 Domain Katmanı

🔹 ToDo.cs

namespace CqrsMediatRDemo.Domain;

public class ToDo
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public string? Description { get; set; }
    public DateTime CreatedAt { get; set; }
}

🔹 ToDoStore.cs

namespace CqrsMediatRDemo.Domain;

public static class ToDoStore
{
    public static List<ToDo> ToDos { get; set; } = new();
    public static int CurrentId = 1;
}

🛠️ Application Katmanı

🔹 CreateToDoCommand.cs

using MediatR;

namespace CqrsMediatRDemo.Application.Features.ToDo;

public record CreateToDoCommand(string Title, string? Description) : IRequest<Unit>;

🔹 CreateToDoHandler.cs

using MediatR;
using CqrsMediatRDemo.Domain;

namespace CqrsMediatRDemo.Application.Features.ToDo;

public class CreateToDoHandler : IRequestHandler<CreateToDoCommand, Unit>
{
    public Task<Unit> Handle(CreateToDoCommand request, CancellationToken cancellationToken)
    {
        var todo = new Domain.ToDo
        {
            Id = ToDoStore.CurrentId++,
            Title = request.Title,
            Description = request.Description,
            CreatedAt = DateTime.UtcNow
        };

        ToDoStore.ToDos.Add(todo);

        return Task.FromResult(Unit.Value);
    }
}

🔹 GetAllToDosQuery.cs

using MediatR;
using CqrsMediatRDemo.Domain;

namespace CqrsMediatRDemo.Application.Features.ToDo;

public record GetAllToDosQuery : IRequest<List<Domain.ToDo>>;

🔹 GetAllToDosHandler.cs

using MediatR;
using CqrsMediatRDemo.Domain;

namespace CqrsMediatRDemo.Application.Features.ToDo;

public class GetAllToDosHandler : IRequestHandler<GetAllToDosQuery, List<Domain.ToDo>>
{
    public Task<List<ToDo>> Handle(GetAllToDosQuery request, CancellationToken cancellationToken)
    {
        return Task.FromResult(ToDoStore.ToDos);
    }
}

🌐 Controller

🔹 ToDoController.cs

using MediatR;
using Microsoft.AspNetCore.Mvc;
using CqrsMediatRDemo.Application.Features.ToDo;

namespace CqrsMediatRDemo.Controllers;

[ApiController]
[Route("api/todos")]
public class ToDoController : ControllerBase
{
    private readonly IMediator _mediator;

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

    [HttpPost]
    public async Task<IActionResult> Create(CreateToDoCommand command)
    {
        await _mediator.Send(command);
        return Created();
    }

    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        var result = await _mediator.Send(new GetAllToDosQuery());
        return Ok(result);
    }
}

❓ Unit Nedir? CreateToDoCommand gibi işlemlerden veri dönmeyeceksen, MediatR Unit tipini kullanır. Bu void’un async versiyonu gibi düşünebilirsin.

public record CreateToDoCommand : IRequest<Unit>;

Handler içinde:

return Unit.Value;

❓ Sık Sorulan Sorular

💬 Her projede CQRS + MediatR kullanılmalı mı?

Hayır. Küçük, tek modüllü projelerde fazla gelir. Asıl gücünü karmaşık yapılarda gösterir.

💬 Service katmanı yoksa ne oluyor?

MediatR zaten bir "aracı" görevi gördüğü için çoğu zaman Service katmanına gerek kalmaz. Handler’lar doğrudan işi yapar.

💬 Test yazması kolay mı?

Evet. Command ve Query handler’ları ayrı ayrı test edilebilir, mock dependency yapısı sayesinde unit test'e çok uygundur.

💬 Performans farkı var mı?

Genellikle ihmal edilebilir düzeydedir. Ancak MediatR gibi katmanlar mikroservislerde değil de, yüksek frekanslı tekil işlemlerde kullanılıyorsa dikkatli olunmalı.


🔚 Kapanış

CQRS + MediatR yapısı, ilk bakışta "fazla katmanlı" gibi görünse de büyüyen projelerde net sorumluluk ayrımı, test kolaylığı ve daha az kafa karışıklığı sağlar.

Ancak:

⚠️ Küçük projelerde gereksiz soyutlama, ileride seni yavaşlatabilir.
Bu yapıyı seçmeden önce gerçekten ihtiyacın olup olmadığını sorgula.

Bu örnek, CQRS + MediatR yapısının temellerini öğrenmen için fazlasıyla yeterli.
Gerisi senin ihtiyacına ve projenin büyüklüğüne göre şekillenir.

🎯 Kafaya takmadan, adım adım öğrendik.
Şimdi sıra sende: Kodla, kurcala, dene.

0
Subscribe to my newsletter

Read articles from Uygar Öztürk Ceylan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Uygar Öztürk Ceylan
Uygar Öztürk Ceylan