Repository Pattern com EF Core: ainda precisamos dele?

Repository Pattern sobre Entity Framework Core aparece em praticamente todo projeto .NET de porte médio para cima, quase por reflexo. Vale parar para perguntar o que essa camada realmente adiciona quando o DbContext já faz boa parte do que ela promete.

O que Repository e Unit of Work prometem originalmente

Repository Pattern nasceu para abstrair a origem dos dados: o resto da aplicação depende de uma interface, não de detalhes de acesso a banco, o que teoricamente permite trocar a implementação por trás sem afetar quem consome. Unit of Work agrupa múltiplas operações em uma única transação lógica, garantindo que ou tudo é persistido, ou nada é.

public interface IPedidoRepository
{
    Task<Pedido?> ObterPorIdAsync(int id);
    void Adicionar(Pedido pedido);
}

Essa promessa fazia todo sentido numa época de acesso a dado via ADO.NET cru, sem nenhuma camada de abstração por baixo.

O DbContext já é um Repository e um Unit of Work

O ponto central da discussão é que DbSet<T> já se comporta como um repositório genérico: oferece consulta via LINQ, adição, remoção e rastreamento de mudança. O DbContext inteiro já se comporta como uma Unit of Work: SaveChangesAsync() persiste, numa única transação, todas as mudanças rastreadas desde a última chamada.

var pedido = await dbContext.Pedidos.FindAsync(id);
pedido.Status = StatusPedido.Enviado;
await dbContext.SaveChangesAsync();

Colocar uma interface de Repository por cima disso frequentemente não abstrai banco de dado nenhum, abstrai o EF Core, que por sua vez já é a abstração sobre o banco. É camada sobre camada, resolvendo um problema que a camada de baixo já resolveu.

Quando essa camada extra ainda compensa

Existem cenários onde repositório customizado sobre EF Core segue justificável. Quando a lógica de consulta é complexa e repetida em vários lugares, centralizar esse LINQ específico numa interface evita duplicação e facilita manutenção. Quando existe intenção real de trocar de provedor de persistência no futuro, não como possibilidade teórica remota, mas como cenário concreto do projeto, uma camada de abstração reduz o atrito dessa troca.

Também vale considerar times grandes, onde restringir o acesso a dado a um conjunto controlado de métodos, em vez de LINQ livre espalhado pela aplicação inteira, ajuda a manter consistência de padrão entre desenvolvedores diferentes.

Testabilidade não é mais o argumento mais forte

Testabilidade costumava ser a justificativa mais citada: repositório mockável permite testar regra de negócio sem banco real. Esse argumento perdeu força com a maturidade de bancos em memória e, principalmente, com Testcontainers, que sobe um banco real, em container, durante o teste de integração. Um teste de integração contra banco real captura comportamento que um mock de repositório nunca captura, como comportamento específico de tradução de query ou de constraint de schema.

await using var container = new PostgreSqlBuilder().Build();
await container.StartAsync();

Isso não elimina todo valor de mock em teste unitário mais isolado, mas reduz a dependência de Repository Pattern especificamente como pré-requisito para testar.

A pergunta que substitui “sempre usar Repository”

A pergunta relevante deixou de ser “esse projeto deveria ter Repository Pattern” como regra fixa. Passou a ser “esse projeto específico tem consulta complexa repetida, intenção real de trocar de provedor, ou necessidade de restringir acesso a dado entre muitos desenvolvedores”. Quando nenhuma dessas condições se aplica, a camada extra tende a só adicionar indireção sem retorno equivalente, sobre uma abstração que o próprio EF Core já entrega.