Persistență prin EF Core în Infrastructure Layer
Doru Bulubasa
06 August 2025

Într-o arhitectură bazată pe principiile Clean Architecture sau DDD (Domain-Driven Design), separarea responsabilităților este esențială. Un aspect important este separarea logicii de acces la date într-un Infrastructure Layer, unde folosim Entity Framework Core pentru a interacționa cu baza de date.

🧩 De ce separăm persistenta în Infrastructure Layer?

  • Menținem codul de business curat și testabil.

  • Permitem ușor testarea logicii folosind mock-uri sau fakes (prin interfețe).

  • Putem schimba motorul de persistență (ex: EF Core → Dapper sau MongoDB) fără să atingem logica de business.


🔧 Pas 1: Definirea unei interfețe comune – IApplicationDbContext

Această interfață va fi folosită în toate zonele care au nevoie să acceseze baza de date, dar fără să cunoască implementarea concretă (adică ApplicationDbContext).

public interface IApplicationDbContext

{

    DbSet<Post> Posts { get; }

    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);

}

Observă că expunem doar ceea ce e necesar (ex: DbSet-uri și metoda de salvare).


🧱 Pas 2: Implementarea concretă – ApplicationDbContext

Această clasă moștenește DbContext din EF Core și implementează interfața IApplicationDbContext.

public class ApplicationDbContext : DbContext, IApplicationDbContext

{

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)

        : base(options) { }

    public DbSet<Post> Posts => Set<Post>();

    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)

        => base.SaveChangesAsync(cancellationToken);

}


🧪 Pas 3: Înregistrarea în DI (Dependency Injection)

services.AddDbContext<ApplicationDbContext>(options =>

    options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));

services.AddScoped<IApplicationDbContext>(provider => provider.GetRequiredService<ApplicationDbContext>());

Astfel, codul care primește IApplicationDbContext (ex: un CommandHandler) nu știe că folosește EF Core – ceea ce face codul testabil și decuplat.


✍️ Exemplu de utilizare în CreatePostCommandHandler

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

        };

        _context.Posts.Add(post);

        await _context.SaveChangesAsync(cancellationToken);

        return post.Id;

    }

}


✅ Concluzie

Prin definirea unei interfețe (IApplicationDbContext) și folosirea sa în codul de business, păstrăm separarea clară între:

  • logica de business (care nu știe nimic despre EF Core)

  • infrastructura de date (care implementează EF Core)

Această separare este fundamentală pentru scalabilitate, testabilitate și întreținerea pe termen lung.