Proiectarea Repository (opțional) pentru scriere

  • Doru Bulubasa
  • 22 September 2025

IPostRepository.AddAsync(), GetByIdAsync() (în Command)

În arhitecturile moderne bazate pe CQRS (Command Query Responsibility Segregation), unul dintre principiile de bază este separarea clară dintre partea de scriere și partea de citire a aplicației. În articolele anterioare, am discutat despre folosirea ApplicationDbContext și despre aplicarea tranzacțiilor în Command Handlers. Astăzi mergem mai departe și analizăm un concept opțional, dar frecvent întâlnit: Repository Pattern pentru scriere.

De ce „opțional”? Pentru că nu există o regulă strictă în CQRS care să impună utilizarea Repository Pattern-ului atunci când lucrăm cu Entity Framework Core. În multe cazuri, accesul direct la DbContext este suficient și chiar mai simplu. Cu toate acestea, folosirea unui Repository poate aduce beneficii în ceea ce privește claritatea codului, testabilitatea și separarea responsabilităților.


🎯 Ce este Repository Pattern?

Repository Pattern reprezintă un strat intermediar între aplicație și sursa de date. El are rolul de a expune o interfață clară și bine definită pentru operațiile de citire sau scriere, ascunzând detaliile implementării interne.

În loc să lucrăm direct cu DbContext, vom apela metodele unui IPostRepository care se ocupă cu gestionarea entităților Post. Această abordare permite:

  • Reducerea dependenței față de EF Core. Codul din Handlers devine mai puțin „cuplat” la o tehnologie anume.

  • Testabilitate mai bună, deoarece putem înlocui repository-ul real cu un mock sau un fake în testele unitare.

  • Claritate, pentru că logica de acces la date este centralizată într-un singur loc.


🛠 Exemplu de interfață – IPostRepository

Să presupunem că avem entitatea Post. Un repository simplu pentru operații de scriere ar putea arăta astfel:

public interface IPostRepository
{
    Task AddAsync(Post post, CancellationToken cancellationToken = default);
    Task<Post?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
}

Interfața definește metodele esențiale de care un Command Handler ar putea avea nevoie: adăugarea unui post nou și căutarea unui post după ID. Nu avem nevoie de foarte multe metode aici – doar ceea ce este strict necesar pentru logica aplicației.


🏗 Implementarea concretă

O implementare a acestui repository folosind EF Core ar putea arăta astfel:

public class PostRepository : IPostRepository
{
    private readonly ApplicationDbContext _context;

    public PostRepository(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task AddAsync(Post post, CancellationToken cancellationToken = default)
    {
        await _context.Posts.AddAsync(post, cancellationToken);
    }

    public async Task<Post?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
    {
        return await _context.Posts
            .FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
    }
}

Observă că implementarea este destul de simplă: repository-ul nu face altceva decât să „îmbrace” operațiile EF Core, dar oferă o interfață mai clară și izolată.


⚙ Utilizare în Command Handler

Să presupunem că avem un CreatePostCommandHandler. În loc să folosim direct ApplicationDbContext, putem injecta IPostRepository:

public class CreatePostCommandHandler : IRequestHandler<CreatePostCommand, Guid>
{
    private readonly IPostRepository _repository;
    private readonly IUnitOfWork _unitOfWork;

    public CreatePostCommandHandler(IPostRepository repository, IUnitOfWork unitOfWork)
    {
        _repository = repository;
        _unitOfWork = unitOfWork;
    }

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

        await _repository.AddAsync(post, cancellationToken);
        await _unitOfWork.SaveChangesAsync(cancellationToken);

        return post.Id;
    }
}

Aici apare și un alt concept interesant: Unit of Work, care poate fi folosit pentru a centraliza apelul SaveChangesAsync(). Dar chiar și fără Unit of Work, un repository oferă deja un nivel suplimentar de claritate.


📌 Avantaje și dezavantaje

Avantaje:

  • Cod mai organizat și mai ușor de citit.

  • Teste unitare mai simple (mock-uim repository-ul).

  • Respectarea principiului „Dependency Inversion” (depindem de interfețe, nu de implementări concrete).

Dezavantaje:

  • Cod suplimentar: repository-ul poate părea redundant dacă doar „îmbracă” metode EF Core deja existente.

  • Un strat în plus de abstractizare, care nu este întotdeauna necesar în aplicațiile mici sau simple.


🔎 Concluzie

Folosirea unui Repository pentru scriere în CQRS este o alegere de arhitectură, nu o regulă absolută. În unele proiecte, accesul direct la DbContext poate fi mai eficient și mai simplu. În altele, introducerea unui IPostRepository aduce claritate, izolare și facilitează testarea.

Important este să îți evaluezi nevoile proiectului și să alegi varianta care se potrivește cel mai bine contextului tău. Repository Pattern rămâne un instrument valoros atunci când vrei să menții codul curat și să respecți principiile DDD.

Scrie un comentariu

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