Persistență prin EF Core în Infrastructure Layer
Î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.