Repository pattern e Service Layer
ResumoIsolamos o acesso a dados atrás de repositórios e criamos uma Service Layer que a apresentação consome. Um Repository Manager e um Service Manager agrupam os repositórios/serviços com inicialização preguiçosa (
Lazy).
1. Objetivos de Aprendizagem#
- Implementar um
RepositoryBasegenérico e repositórios específicos. - Agrupar repositórios/serviços em managers.
- Registrar tudo no contêiner de DI com o tempo de vida correto.
2. Pré-requisitos#
RepositoryContexte entidades (tópico 17.2).
3. Conceito#
O Repository Pattern abstrai o acesso a dados: o restante da aplicação fala com interfaces (IProdutoRepository), não com o DbContext. A Service Layer contém a lógica de aplicação e mapeia entidades para DTOs (Módulo 18).
O porquê do manager com Lazy<T>: um controller pode injetar um único IRepositoryManager e acessar manager.Produto ou manager.Categoria sob demanda, instanciando o repositório só quando usado.
4. Mão na Massa#
4.1. Setup#
Sem novos pacotes.
4.2. Implementação Passo a Passo#
- Base genérica em
Catalog.Repository:
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
public abstract class RepositoryBase<T> where T : class
{
protected readonly RepositoryContext Context;
protected RepositoryBase(RepositoryContext context) => Context = context;
public IQueryable<T> FindAll(bool trackChanges) =>
trackChanges ? Context.Set<T>() : Context.Set<T>().AsNoTracking();
public IQueryable<T> FindByCondition(Expression<Func<T, bool>> expr, bool trackChanges) =>
trackChanges ? Context.Set<T>().Where(expr) : Context.Set<T>().Where(expr).AsNoTracking();
public void Create(T entity) => Context.Set<T>().Add(entity);
public void Delete(T entity) => Context.Set<T>().Remove(entity);
}
- Interface e repositório de produto:
public interface IProdutoRepository
{
Task<IEnumerable<Produto>> GetProdutosAsync(Guid categoriaId, bool trackChanges);
}
public class ProdutoRepository : RepositoryBase<Produto>, IProdutoRepository
{
public ProdutoRepository(RepositoryContext context) : base(context) { }
public async Task<IEnumerable<Produto>> GetProdutosAsync(Guid categoriaId, bool trackChanges) =>
await FindByCondition(p => p.CategoriaId == categoriaId, trackChanges)
.OrderBy(p => p.Nome)
.ToListAsync();
}
- Manager com
Lazy:
public interface IRepositoryManager
{
IProdutoRepository Produto { get; }
Task SaveAsync();
}
public class RepositoryManager : IRepositoryManager
{
private readonly RepositoryContext _context;
private readonly Lazy<IProdutoRepository> _produto;
public RepositoryManager(RepositoryContext context)
{
_context = context;
_produto = new Lazy<IProdutoRepository>(() => new ProdutoRepository(context));
}
public IProdutoRepository Produto => _produto.Value;
public async Task SaveAsync() => await _context.SaveChangesAsync();
}
- Registro:
public static void ConfigureRepositoryManager(this IServiceCollection s) =>
s.AddScoped<IRepositoryManager, RepositoryManager>();
4.3. Executando#
dotnet build
5. Exemplo Completo#
Program.cs (trecho):
builder.Services.ConfigureSqlContext(builder.Configuration);
builder.Services.ConfigureRepositoryManager();
6. Boas Práticas e Armadilhas#
| Faça | Evite |
|---|---|
Usar AsNoTracking em leituras (via trackChanges: false) | Rastrear tudo, degradando a performance de consultas somente-leitura |
Registrar repositórios/serviços como Scoped | Singleton em algo que depende do DbContext |
Chamar SaveAsync uma vez por unidade de trabalho | Salvar dentro de cada método de repositório |
Atençãoo Repository Pattern sobre EF Core é discutível — o
DbContextjá é um Unit of Work + Repository. Aplique-o quando ele agrega valor real (testabilidade, isolamento), não por dogma.
7. Segurança e Produção#
- Mantenha
trackChanges: falsecomo padrão em leituras públicas para reduzir uso de memória e risco de updates acidentais. - Regras de negócio e autorização ficam na Service Layer, não no repositório.
8. Exercícios#
- Fácil: crie
ICategoriaRepositorye adicione-o ao manager. - Médio: implemente uma Service Layer (
IProdutoService) que use oIRepositoryManager. - Desafio: escreva um teste unitário do serviço usando um
IRepositoryManagermockado.
9. Resumo#
Isolamos o acesso a dados com RepositoryBase genérico e repositórios específicos, agrupados por um manager Lazy. A Service Layer intermedeia a apresentação, mantendo o fluxo Onion intacto.
10. Próximos Passos#
Módulo 18: tratamos requisições GET com controllers, roteamento, DTOs e AutoMapper.
11. Referências#
- Microsoft Learn — Padrões de infraestrutura de persistência
- Microsoft Learn — Consultas sem rastreamento (AsNoTracking)
- Microsoft Learn — Injeção de dependência