Implementarea bazelor CQRS în .NET

  • Doru Bulubasa
  • 09 July 2025

După ce am înțeles conceptele de bază și structura recomandată, e timpul să trecem la implementare. În această etapă, vom configura MediatR și vom crea primele comenzi și interogări (queries) într-o aplicație .NET, folosind un exemplu simplu: Post.


3️⃣ Instalarea MediatR

Pentru a separa responsabilitățile și a evita legături directe între layere, folosim MediatR – o bibliotecă care facilitează trimiterea comenzilor și interogărilor către handler-ele dedicate.

🛠️ Instalare

În proiectul MyApp.Application, adaugă MediatR:

dotnet add package MediatR

dotnet add package MediatR.Extensions.Microsoft.DependencyInjection

În MyApp.API (sau proiectul de UI/API), configurează serviciile în Program.cs:

builder.Services.AddMediatR(cfg =>

    cfg.RegisterServicesFromAssembly(typeof(MyApp.Application.AssemblyReference).Assembly));

Adaugă o clasă de referință în MyApp.Application:

namespace MyApp.Application;

public static class AssemblyReference { }


4️⃣ Crearea unei comenzi simple: CreatePostCommand

🧾 Command: CreatePostCommand.cs

using MediatR;

public record CreatePostCommand(string Title, string Content) : IRequest<Guid>;

IRequest<Guid> înseamnă că această comandă va returna un Guid (ID-ul postării create)


🧠 Handler: CreatePostCommandHandler.cs

public class CreatePostCommandHandler : IRequestHandler<CreatePostCommand, Guid>
{
    private readonly IApplicationDbContext _context;

    public CreatePostCommandHandler(IApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<Guid> Handle(CreatePostCommand request, CancellationToken cancellationToken)
    {
        var post = new Post
        {
            Id = Guid.NewGuid(),
            Title = request.Title,
            Content = request.Content,
            CreatedAt = DateTime.UtcNow
        };

        _context.Posts.Add(post);
        await _context.SaveChangesAsync(cancellationToken);

        return post.Id;
    }
}


5️⃣ Separarea completă pentru o interogare: GetPostByIdQuery

🔍 Query: GetPostByIdQuery.cs

using MediatR;

public record GetPostByIdQuery(Guid Id) : IRequest<PostDto>;

PostDto este un obiect doar pentru afișare – fără logică de business.


🔄 Handler: GetPostByIdQueryHandler.cs

public class GetPostByIdQueryHandler : IRequestHandler<GetPostByIdQuery, PostDto?>
{
    private readonly IApplicationDbContext _context;

    public GetPostByIdQueryHandler(IApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<PostDto?> Handle(GetPostByIdQuery request, CancellationToken cancellationToken)
    {
        var post = await _context.Posts
            .AsNoTracking()
            .Where(p => p.Id == request.Id)
            .Select(p => new PostDto(p.Id, p.Title, p.Content, p.CreatedAt))
            .FirstOrDefaultAsync(cancellationToken);

        return post;
    }
}


📦 PostDto.cs (în Application)

public record PostDto(Guid Id, string Title, string Content, DateTime CreatedAt);


6️⃣ Rezumat: IRequest și IRequest

Tip de request

Interfață

Returnează

Command (scriere)

IRequest<Unit>

Nimic (Unit.Value)

Command cu rezultat

IRequest<Guid>

De exemplu: ID-ul creat

Query (citire)

IRequest<PostDto>

Un rezultat de tip DTO


🧪 Exemplu de folosire din Controller / UI

[HttpPost]
public async Task<IActionResult> Create(CreatePostCommand command)
{
    var id = await _mediator.Send(command);
    return CreatedAtAction(nameof(GetById), new { id }, null);
}

[HttpGet("{id}")]
public async Task<ActionResult<PostDto>> GetById(Guid id)
{
    var post = await _mediator.Send(new GetPostByIdQuery(id));
    return post is not null ? Ok(post) : NotFound();
}


🧠 Concluzie

În această etapă am implementat primele piese esențiale ale unui sistem CQRS:

  • ✅ Comenzi care modifică starea aplicației

  • ✅ Interogări separate pentru citire

  • ✅ Handler-e dedicate și independente

  • ✅ MediatR ca intermediar central

🏁 Ce urmează

În următorul articol vom adăuga:

  • ✅ Validări cu FluentValidation

  • ✅ Mapping între DTO-uri și entități

  • ✅ Separarea clară între WriteModel și ReadModel

Scrie un comentariu

Adresa de mail nu va fi publicata. Campurile obligatorii sunt marcate cu *